Añadido filtro de prohibicion de caminos por poligono dibujado
This commit is contained in:
@@ -1400,7 +1400,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260903b)";
|
||||
private const string _versionApp = "(v-20260907a)";
|
||||
|
||||
|
||||
private double? _paradaElevIdee;
|
||||
|
||||
@@ -1659,6 +1659,12 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
var lat = e.location.latitude;
|
||||
var lon = e.location.longitude;
|
||||
|
||||
if (_modoDibujoExclusionCaminata)
|
||||
{
|
||||
await OnExclusionPolygonMapClick(lat, lon);
|
||||
return;
|
||||
}
|
||||
|
||||
// Inspector (Z) sigue siendo click simple inmediato
|
||||
if (_inspectorActivo)
|
||||
@@ -1921,6 +1927,8 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
|
||||
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
||||
UsarValhallaParaCaminatas = _walkProvider == ProveedorCaminata.Valhalla,
|
||||
PoligonosExclusionCaminata = ObtenerPoligonosExclusionCaminata(),
|
||||
VelocidadBusKmH = _velocidadBusKmH,
|
||||
MinutosPorTransbordo = _minutosTransbordo,
|
||||
MargenTransbordoSegundos = Math.Clamp(_margenTransbordoSegundos, 0, 1800),
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace RutasDBUS.Components.Pages;
|
||||
|
||||
public partial class PlanificadorRutas
|
||||
{
|
||||
private bool _modoDibujoExclusionCaminata;
|
||||
private bool _poligonoExclusionCaminataCerrado;
|
||||
private readonly List<double[]> _verticesExclusionCaminata = new();
|
||||
|
||||
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 List<List<double[]>>? ObtenerPoligonosExclusionCaminata()
|
||||
{
|
||||
if (!PoligonoExclusionCaminataValido)
|
||||
return null;
|
||||
|
||||
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 IniciarDibujoExclusionCaminataAsync()
|
||||
{
|
||||
if (!PuedeProbarExclusionCaminata)
|
||||
return;
|
||||
|
||||
_modoDibujoExclusionCaminata = true;
|
||||
_poligonoExclusionCaminataCerrado = false;
|
||||
_verticesExclusionCaminata.Clear();
|
||||
await JS.InvokeVoidAsync("rt.beginExclusionPolygonEditor");
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task CancelarDibujoExclusionCaminataAsync()
|
||||
{
|
||||
_modoDibujoExclusionCaminata = false;
|
||||
_poligonoExclusionCaminataCerrado = false;
|
||||
_verticesExclusionCaminata.Clear();
|
||||
await JS.InvokeVoidAsync("rt.clearExclusionPolygonEditor");
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeshacerVerticeExclusionCaminataAsync()
|
||||
{
|
||||
if (_verticesExclusionCaminata.Count == 0 || _poligonoExclusionCaminataCerrado)
|
||||
return;
|
||||
|
||||
_verticesExclusionCaminata.RemoveAt(_verticesExclusionCaminata.Count - 1);
|
||||
await ActualizarEditorExclusionCaminataAsync();
|
||||
}
|
||||
|
||||
private async Task CerrarPoligonoExclusionCaminataAsync()
|
||||
{
|
||||
if (_verticesExclusionCaminata.Count < 3)
|
||||
return;
|
||||
|
||||
_poligonoExclusionCaminataCerrado = true;
|
||||
await JS.InvokeVoidAsync("rt.closeExclusionPolygonEditor");
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task CalcularRutaEvitandoPoligonoCaminataAsync()
|
||||
{
|
||||
if (!PoligonoExclusionCaminataValido)
|
||||
return;
|
||||
|
||||
_modoDibujoExclusionCaminata = false;
|
||||
await JS.InvokeVoidAsync("rt.finishExclusionPolygonEditor");
|
||||
_omitirRegistroHistorialEnSiguienteCalculo = true;
|
||||
await CalcularRutaCompletaAsync();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnExclusionPolygonMapClick(double latitud, double longitud)
|
||||
{
|
||||
if (!_modoDibujoExclusionCaminata || _poligonoExclusionCaminataCerrado)
|
||||
return;
|
||||
|
||||
_verticesExclusionCaminata.Add(new[] { latitud, longitud });
|
||||
await ActualizarEditorExclusionCaminataAsync();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public Task OnExclusionPolygonVertexDragged(int indice, double latitud, double longitud)
|
||||
{
|
||||
if (indice >= 0 && indice < _verticesExclusionCaminata.Count)
|
||||
{
|
||||
_verticesExclusionCaminata[indice][0] = latitud;
|
||||
_verticesExclusionCaminata[indice][1] = longitud;
|
||||
}
|
||||
|
||||
return InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task ActualizarEditorExclusionCaminataAsync()
|
||||
{
|
||||
await JS.InvokeVoidAsync("rt.updateExclusionPolygonEditor", _verticesExclusionCaminata);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -847,6 +847,54 @@
|
||||
</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">
|
||||
@if (alt.EsSoloAPie)
|
||||
|
||||
@@ -37,6 +37,12 @@ public sealed class ParametrosPlanificadorRuta
|
||||
// Divisor aplicado a la distancia en bus dentro del score equilibrado.
|
||||
public double FactorEquilibradoAndarBus { get; set; } = 6.0;
|
||||
|
||||
// Proveedor usado para todas las caminatas que intervienen en la planificacion.
|
||||
public bool UsarValhallaParaCaminatas { get; set; } = false;
|
||||
|
||||
// Zonas temporales que Valhalla debe excluir al calcular caminatas.
|
||||
public List<List<double[]>>? PoligonosExclusionCaminata { get; set; }
|
||||
|
||||
// selección candidatas
|
||||
public int RadioParadasCandidatasMetros { get; set; } = 1200;
|
||||
public int MaxParadasCandidatasPorLado { get; set; } = 12;
|
||||
|
||||
@@ -32,7 +32,8 @@ public class ClienteValhalla : IClienteValhalla
|
||||
double longitudDesde,
|
||||
double latitudHasta,
|
||||
double longitudHasta,
|
||||
string costing = "pedestrian")
|
||||
string costing = "pedestrian",
|
||||
List<List<double[]>>? excludePolygons = null)
|
||||
{
|
||||
var cuerpo = new SolicitudValhalla
|
||||
{
|
||||
@@ -43,6 +44,7 @@ public class ClienteValhalla : IClienteValhalla
|
||||
},
|
||||
PerfilCoste = string.IsNullOrWhiteSpace(costing) ? "pedestrian" : costing,
|
||||
FormatoGeometria = "polyline6",
|
||||
PoligonosExclusion = excludePolygons,
|
||||
OpcionesIndicaciones = new OpcionesIndicacionesValhalla
|
||||
{
|
||||
Idioma = "es-ES",
|
||||
@@ -125,6 +127,9 @@ public class ClienteValhalla : IClienteValhalla
|
||||
[JsonPropertyName("shape_format")]
|
||||
public string FormatoGeometria { get; set; } = "polyline6";
|
||||
|
||||
[JsonPropertyName("exclude_polygons")]
|
||||
public List<List<double[]>>? PoligonosExclusion { get; set; }
|
||||
|
||||
[JsonPropertyName("directions_options")]
|
||||
public OpcionesIndicacionesValhalla OpcionesIndicaciones { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ public interface IClienteValhalla
|
||||
double longitudDesde,
|
||||
double latitudHasta,
|
||||
double longitudHasta,
|
||||
string costing = "pedestrian"); // "pedestrian", "bicycle", etc.
|
||||
string costing = "pedestrian",
|
||||
List<List<double[]>>? excludePolygons = null); // "pedestrian", "bicycle", etc.
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using RutasDBUS.Modelos.Planificacion;
|
||||
using RutasDBUS.Modelos.RedBus;
|
||||
@@ -24,6 +25,8 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
|
||||
private static readonly ConcurrentDictionary<string, (double[,]? linea, double distancia, double duracion)> _cacheCaminatas =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly ConcurrentDictionary<string, (double[,]? linea, double distancia, double duracion)> _cacheCaminatasConExclusion =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly ConcurrentDictionary<string, double> _cacheDuracionBusRefinamiento =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly ConcurrentDictionary<string, (bool ok, DateTime salida, DateTime llegada)> _cacheSegmentosHorarios =
|
||||
@@ -2063,7 +2066,12 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
await semaforo.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
var caminata = await WalkOsrmCached(latitud, longitud, candidata.Parada.Latitud, candidata.Parada.Longitud);
|
||||
var caminata = await WalkCaminataCached(
|
||||
latitud,
|
||||
longitud,
|
||||
candidata.Parada.Latitud,
|
||||
candidata.Parada.Longitud,
|
||||
parametros);
|
||||
if (caminata.distancia > 0)
|
||||
{
|
||||
candidata.DistanciaAPie = caminata.distancia;
|
||||
@@ -2200,7 +2208,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
=> $"{patron.CodigoLinea}|{patron.CodigoItinerario}|{patron.Sentido}";
|
||||
|
||||
/// <summary>
|
||||
/// Crea las dos alternativas de solo a pie.
|
||||
/// Crea las dos alternativas independientes de solo a pie.
|
||||
/// </summary>
|
||||
private async Task<List<AlternativaRuta>> CrearAlternativasSoloPieAsync(
|
||||
double[] origenLatLon,
|
||||
@@ -2209,7 +2217,11 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
{
|
||||
var alternativas = new List<AlternativaRuta>(2);
|
||||
|
||||
var osrm = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], destinoLatLon[0], destinoLatLon[1]);
|
||||
var osrm = await WalkOsrmCached(
|
||||
origenLatLon[0],
|
||||
origenLatLon[1],
|
||||
destinoLatLon[0],
|
||||
destinoLatLon[1]);
|
||||
if (osrm.linea is not null && osrm.distancia > 0)
|
||||
{
|
||||
var duracion = UtilidadesTransitoDbus.EstimarDuracionPieSegundos(osrm.distancia, parametros);
|
||||
@@ -2229,7 +2241,12 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
});
|
||||
}
|
||||
|
||||
var valhalla = await WalkValhallaCached(origenLatLon[0], origenLatLon[1], destinoLatLon[0], destinoLatLon[1]);
|
||||
var valhalla = await WalkValhallaCached(
|
||||
origenLatLon[0],
|
||||
origenLatLon[1],
|
||||
destinoLatLon[0],
|
||||
destinoLatLon[1],
|
||||
parametros.PoligonosExclusionCaminata);
|
||||
if (valhalla.linea is not null && valhalla.distancia > 0)
|
||||
{
|
||||
var duracion = UtilidadesTransitoDbus.EstimarDuracionPieSegundos(valhalla.distancia, parametros);
|
||||
@@ -2365,7 +2382,12 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
var total = 0.0;
|
||||
var cursor = instanteSalida;
|
||||
|
||||
var acceso = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
|
||||
var acceso = await WalkCaminataCached(
|
||||
origenLatLon[0],
|
||||
origenLatLon[1],
|
||||
paradaInicioBus.Latitud,
|
||||
paradaInicioBus.Longitud,
|
||||
parametros);
|
||||
alternativa.DistanciaPieInicioAprox = acceso.distancia > 0
|
||||
? acceso.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
|
||||
@@ -2423,11 +2445,12 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
distanciaUnion > UmbralCosturaVisualMetros &&
|
||||
distanciaUnion <= UmbralUnionPeatonalAutomaticaMetros)
|
||||
{
|
||||
var transferencia = await WalkOsrmCached(
|
||||
var transferencia = await WalkCaminataCached(
|
||||
puntoUltimaLlegada[0],
|
||||
puntoUltimaLlegada[1],
|
||||
puntoInicioTramo![0],
|
||||
puntoInicioTramo[1]);
|
||||
puntoInicioTramo[1],
|
||||
parametros);
|
||||
|
||||
var distanciaTransferencia = transferencia.distancia > 0
|
||||
? transferencia.distancia
|
||||
@@ -2560,11 +2583,12 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
paradaFinBus);
|
||||
alternativa.DestinoParada = paradaFinBus;
|
||||
|
||||
var salidaFinal = await WalkOsrmCached(
|
||||
var salidaFinal = await WalkCaminataCached(
|
||||
paradaFinBus.Latitud,
|
||||
paradaFinBus.Longitud,
|
||||
destinoLatLon[0],
|
||||
destinoLatLon[1]);
|
||||
destinoLatLon[1],
|
||||
parametros);
|
||||
alternativa.DistanciaPieFinAprox = salidaFinal.distancia > 0
|
||||
? salidaFinal.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(
|
||||
@@ -2776,12 +2800,14 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
if (string.IsNullOrWhiteSpace(paradaPrimera.Codigo) || string.IsNullOrWhiteSpace(paradaDirecta.Codigo))
|
||||
return;
|
||||
|
||||
var accesoPrimero = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], paradaPrimera.Latitud, paradaPrimera.Longitud);
|
||||
var accesoPrimero = await WalkCaminataCached(
|
||||
origenLatLon[0], origenLatLon[1], paradaPrimera.Latitud, paradaPrimera.Longitud, parametros);
|
||||
var distanciaPrimero = accesoPrimero.distancia > 0
|
||||
? accesoPrimero.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], paradaPrimera.Latitud, paradaPrimera.Longitud);
|
||||
|
||||
var accesoDirecto = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], paradaDirecta.Latitud, paradaDirecta.Longitud);
|
||||
var accesoDirecto = await WalkCaminataCached(
|
||||
origenLatLon[0], origenLatLon[1], paradaDirecta.Latitud, paradaDirecta.Longitud, parametros);
|
||||
var distanciaDirecta = accesoDirecto.distancia > 0
|
||||
? accesoDirecto.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], paradaDirecta.Latitud, paradaDirecta.Longitud);
|
||||
@@ -3026,7 +3052,8 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
if (parada is null)
|
||||
return null;
|
||||
|
||||
var acceso = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], parada.Latitud, parada.Longitud);
|
||||
var acceso = await WalkCaminataCached(
|
||||
origenLatLon[0], origenLatLon[1], parada.Latitud, parada.Longitud, parametros);
|
||||
var distanciaWalk = acceso.distancia > 0
|
||||
? acceso.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], parada.Latitud, parada.Longitud);
|
||||
@@ -3082,7 +3109,8 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
if (parada is null)
|
||||
return null;
|
||||
|
||||
var salidaFinal = await WalkOsrmCached(parada.Latitud, parada.Longitud, destinoLatLon[0], destinoLatLon[1]);
|
||||
var salidaFinal = await WalkCaminataCached(
|
||||
parada.Latitud, parada.Longitud, destinoLatLon[0], destinoLatLon[1], parametros);
|
||||
var distanciaWalk = salidaFinal.distancia > 0
|
||||
? salidaFinal.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(parada.Latitud, parada.Longitud, destinoLatLon[0], destinoLatLon[1]);
|
||||
@@ -3791,6 +3819,26 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Devuelve una caminata usando el proveedor elegido para esta planificación.
|
||||
/// </summary>
|
||||
private Task<(double[,]? linea, double distancia, double duracion)> WalkCaminataCached(
|
||||
double latitudOrigen,
|
||||
double longitudOrigen,
|
||||
double latitudDestino,
|
||||
double longitudDestino,
|
||||
ParametrosPlanificadorRuta parametros)
|
||||
{
|
||||
return parametros.UsarValhallaParaCaminatas
|
||||
? WalkValhallaCached(
|
||||
latitudOrigen,
|
||||
longitudOrigen,
|
||||
latitudDestino,
|
||||
longitudDestino,
|
||||
parametros.PoligonosExclusionCaminata)
|
||||
: WalkOsrmCached(latitudOrigen, longitudOrigen, latitudDestino, longitudDestino);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Devuelve una caminata desde OSRM con cache.
|
||||
/// </summary>
|
||||
@@ -3831,8 +3879,47 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
double latitudOrigen,
|
||||
double longitudOrigen,
|
||||
double latitudDestino,
|
||||
double longitudDestino)
|
||||
double longitudDestino,
|
||||
List<List<double[]>>? excludePolygons = null)
|
||||
{
|
||||
if (excludePolygons is { Count: > 0 })
|
||||
{
|
||||
// Una zona claramente alejada no puede cambiar razonablemente este trayecto.
|
||||
// Evitamos enviar a Valhalla una busqueda restringida innecesaria.
|
||||
if (!ExclusionPuedeAfectarTrayecto(
|
||||
latitudOrigen, longitudOrigen, latitudDestino, longitudDestino, excludePolygons))
|
||||
{
|
||||
return await WalkValhallaCached(
|
||||
latitudOrigen, longitudOrigen, latitudDestino, longitudDestino);
|
||||
}
|
||||
|
||||
var claveExclusion = BuildWalkKey("valhalla-exclusion", latitudOrigen, longitudOrigen, latitudDestino, longitudDestino)
|
||||
+ "|" + JsonSerializer.Serialize(excludePolygons);
|
||||
if (_cacheCaminatasConExclusion.TryGetValue(claveExclusion, out var cacheadoConExclusion))
|
||||
return cacheadoConExclusion;
|
||||
|
||||
try
|
||||
{
|
||||
var (linea, distancia, duracion) = await _clienteValhalla.ObtenerRutaAsync(
|
||||
latitudOrigen,
|
||||
longitudOrigen,
|
||||
latitudDestino,
|
||||
longitudDestino,
|
||||
costing: "pedestrian",
|
||||
excludePolygons: excludePolygons);
|
||||
|
||||
var valor = (linea, distancia, duracion);
|
||||
_cacheCaminatasConExclusion[claveExclusion] = valor;
|
||||
return valor;
|
||||
}
|
||||
catch
|
||||
{
|
||||
var vacio = (linea: (double[,]?)null, distancia: 0.0, duracion: 0.0);
|
||||
_cacheCaminatasConExclusion[claveExclusion] = vacio;
|
||||
return vacio;
|
||||
}
|
||||
}
|
||||
|
||||
var clave = BuildWalkKey("valhalla", latitudOrigen, longitudOrigen, latitudDestino, longitudDestino);
|
||||
if (_cacheCaminatas.TryGetValue(clave, out var cacheado))
|
||||
return cacheado;
|
||||
@@ -3858,6 +3945,41 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ExclusionPuedeAfectarTrayecto(
|
||||
double latitudOrigen,
|
||||
double longitudOrigen,
|
||||
double latitudDestino,
|
||||
double longitudDestino,
|
||||
List<List<double[]>> poligonos)
|
||||
{
|
||||
const double umbralMetros = 2500.0;
|
||||
var escalaLat = 111_320.0;
|
||||
var escalaLon = Math.Max(1.0, escalaLat * Math.Cos(latitudOrigen * Math.PI / 180.0));
|
||||
var ax = 0.0;
|
||||
var ay = 0.0;
|
||||
var bx = (longitudDestino - longitudOrigen) * escalaLon;
|
||||
var by = (latitudDestino - latitudOrigen) * escalaLat;
|
||||
var ab2 = bx * bx + by * by;
|
||||
|
||||
foreach (var poligono in poligonos)
|
||||
{
|
||||
foreach (var punto in poligono)
|
||||
{
|
||||
if (punto is null || punto.Length < 2) continue;
|
||||
// Valhalla recibe los vertices como [longitud, latitud].
|
||||
var px = (punto[0] - longitudOrigen) * escalaLon;
|
||||
var py = (punto[1] - latitudOrigen) * escalaLat;
|
||||
var t = ab2 > 0 ? Math.Clamp((px * bx + py * by) / ab2, 0.0, 1.0) : 0.0;
|
||||
var dx = px - (ax + t * bx);
|
||||
var dy = py - (ay + t * by);
|
||||
if (Math.Sqrt(dx * dx + dy * dy) <= umbralMetros)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye una clave estable de cache para caminatas.
|
||||
/// </summary>
|
||||
|
||||
@@ -1132,6 +1132,76 @@ html, body {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.route-exclusion-tool {
|
||||
margin: 0 0 .55rem;
|
||||
padding: .48rem .6rem;
|
||||
border: 1px solid rgba(248, 113, 113, .42);
|
||||
border-left: 3px solid #ef4444;
|
||||
border-radius: 6px;
|
||||
background: rgba(127, 29, 29, .16);
|
||||
}
|
||||
|
||||
.route-exclusion-tool__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: .6rem;
|
||||
margin-bottom: .28rem;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: .35rem;
|
||||
margin-top: .35rem;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__close {
|
||||
border-color: rgba(248, 113, 113, .6);
|
||||
color: #fecaca;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__close:not(:disabled):hover {
|
||||
border-color: #f87171;
|
||||
background: rgba(185, 28, 28, .25);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__close:disabled {
|
||||
opacity: .45;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__calculate {
|
||||
border: 1px solid rgba(125, 211, 252, .7);
|
||||
border-radius: 999px;
|
||||
background: #1d4ed8;
|
||||
padding: .3rem .9rem;
|
||||
font-size: .78rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__calculate:hover {
|
||||
background: #2563eb;
|
||||
border-color: #bae6fd;
|
||||
}
|
||||
|
||||
.route-exclusion-tool__hint {
|
||||
color: var(--muted-text, #a1a1aa);
|
||||
font-size: .82rem;
|
||||
}
|
||||
|
||||
.rt-exclusion-vertex {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
background: #dc2626;
|
||||
box-shadow: 0 0 0 2px #7f1d1d, 0 1px 4px rgba(0, 0, 0, .55);
|
||||
}
|
||||
|
||||
.taxibus-notice {
|
||||
margin: 0 0 .45rem;
|
||||
padding: .38rem .5rem;
|
||||
|
||||
@@ -330,6 +330,7 @@ window.rt.drawRoute = function (id, coords, options) {
|
||||
const attachRouteClick = function (line) {
|
||||
line.on("click", function (e) {
|
||||
try {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
if (window.rt.handleInspectorLayerClick(e)) return;
|
||||
|
||||
if (e && e.originalEvent) {
|
||||
@@ -464,6 +465,7 @@ window.rt.drawRoute = function (id, coords, options) {
|
||||
segmentLine.on("mouseover", updateTooltipAtCursor);
|
||||
segmentLine.on("mousemove", updateTooltipAtCursor);
|
||||
segmentLine.on("click", function (e) {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
if (window.rt.handleInspectorLayerClick(e)) return;
|
||||
|
||||
window.rt.selectElevationSegment(
|
||||
@@ -655,6 +657,93 @@ window.rt.setDotNetHelper = function (dotNetHelper) {
|
||||
window.rt._dotNetHelper = dotNetHelper;
|
||||
};
|
||||
|
||||
// Editor temporal de zonas que se excluiran de las caminatas de Valhalla.
|
||||
window.rt._exclusionPolygonLayer = null;
|
||||
window.rt._exclusionPolygon = null;
|
||||
window.rt._exclusionPolygonMarkers = [];
|
||||
window.rt._exclusionEditMode = false;
|
||||
|
||||
window.rt.beginExclusionPolygonEditor = function () {
|
||||
const map = window.__rtLeafletMap;
|
||||
if (!map || !window.L) return;
|
||||
|
||||
window.rt.clearExclusionPolygonEditor();
|
||||
window.rt._exclusionEditMode = true;
|
||||
window.rt._exclusionPolygonLayer = window.L.layerGroup().addTo(map);
|
||||
window.rt._exclusionPolygon = window.L.polygon([], {
|
||||
color: "#dc2626",
|
||||
weight: 3,
|
||||
opacity: 0.95,
|
||||
fillColor: "#ef4444",
|
||||
fillOpacity: 0.2,
|
||||
interactive: false
|
||||
}).addTo(window.rt._exclusionPolygonLayer);
|
||||
};
|
||||
|
||||
window.rt.updateExclusionPolygonEditor = function (points) {
|
||||
const map = window.__rtLeafletMap;
|
||||
if (!map || !window.L) return;
|
||||
if (!window.rt._exclusionPolygonLayer) window.rt.beginExclusionPolygonEditor();
|
||||
|
||||
const polygonPoints = Array.isArray(points)
|
||||
? points.filter(p => Array.isArray(p) && Number.isFinite(Number(p[0])) && Number.isFinite(Number(p[1])))
|
||||
.map(p => [Number(p[0]), Number(p[1])])
|
||||
: [];
|
||||
|
||||
window.rt._exclusionPolygon.setLatLngs(polygonPoints);
|
||||
window.rt._exclusionPolygonMarkers.forEach(marker => marker.remove());
|
||||
window.rt._exclusionPolygonMarkers = polygonPoints.map((point, index) => {
|
||||
const marker = window.L.marker(point, {
|
||||
draggable: true,
|
||||
autoPan: true,
|
||||
bubblingMouseEvents: false,
|
||||
icon: window.L.divIcon({
|
||||
className: "rt-exclusion-vertex",
|
||||
html: "",
|
||||
iconSize: [18, 18],
|
||||
iconAnchor: [9, 9]
|
||||
})
|
||||
}).addTo(window.rt._exclusionPolygonLayer);
|
||||
|
||||
marker.on("click", event => {
|
||||
if (event && event.originalEvent) event.originalEvent.stopPropagation();
|
||||
});
|
||||
marker.on("drag", event => {
|
||||
const latLng = event.target.getLatLng();
|
||||
const current = window.rt._exclusionPolygon.getLatLngs()[0] || [];
|
||||
if (current[index]) {
|
||||
current[index] = latLng;
|
||||
window.rt._exclusionPolygon.setLatLngs([current]);
|
||||
}
|
||||
});
|
||||
marker.on("dragend", event => {
|
||||
const latLng = event.target.getLatLng();
|
||||
window.rt._dotNetHelper?.invokeMethodAsync(
|
||||
"OnExclusionPolygonVertexDragged", index, latLng.lat, latLng.lng);
|
||||
});
|
||||
return marker;
|
||||
});
|
||||
};
|
||||
|
||||
window.rt.closeExclusionPolygonEditor = function () {
|
||||
if (window.rt._exclusionPolygon) {
|
||||
window.rt._exclusionPolygon.setStyle({ fillOpacity: 0.28 });
|
||||
}
|
||||
};
|
||||
|
||||
window.rt.finishExclusionPolygonEditor = function () {
|
||||
window.rt._exclusionEditMode = false;
|
||||
};
|
||||
|
||||
window.rt.clearExclusionPolygonEditor = function () {
|
||||
window.rt._exclusionPolygonMarkers.forEach(marker => marker.remove());
|
||||
window.rt._exclusionPolygonMarkers = [];
|
||||
window.rt._exclusionPolygonLayer?.remove();
|
||||
window.rt._exclusionPolygonLayer = null;
|
||||
window.rt._exclusionPolygon = null;
|
||||
window.rt._exclusionEditMode = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1025,6 +1114,7 @@ window.rt.drawPreviewLine = function (id, coords, options) {
|
||||
// ✅ Hacer la línea clicable y avisar a Blazor
|
||||
line.on("click", function (e) {
|
||||
try {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
if (window.rt.handleInspectorLayerClick(e)) return;
|
||||
|
||||
if (e && e.originalEvent) {
|
||||
@@ -1213,9 +1303,10 @@ window.rt.createHighlightedStopMarker = function (stop, pane, options) {
|
||||
});
|
||||
|
||||
if (stop.code && window.rt._dotNetHelper) {
|
||||
marker.on("click", function (event) {
|
||||
try {
|
||||
if (window.rt.handleInspectorLayerClick(event)) return;
|
||||
marker.on("click", function (event) {
|
||||
try {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
if (window.rt.handleInspectorLayerClick(event)) return;
|
||||
|
||||
if (event && event.originalEvent) {
|
||||
if (event.originalEvent.preventDefault) event.originalEvent.preventDefault();
|
||||
@@ -1407,6 +1498,7 @@ window.rt.setEndpointMarker = function (kind, lat, lon, options) {
|
||||
};
|
||||
|
||||
marker.on("click", function (event) {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
if (window.rt.handleInspectorLayerClick(event)) return;
|
||||
stopEvent(event);
|
||||
});
|
||||
@@ -1493,6 +1585,7 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
|
||||
className: "rt-inspector-tooltip"
|
||||
});
|
||||
puntoZ.on("click", function (event) {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
window.rt.handleInspectorLayerClick(event);
|
||||
});
|
||||
puntoZ.addTo(window.rt._inspectorLayer);
|
||||
@@ -1568,6 +1661,7 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
|
||||
className: "rt-inspector-tooltip"
|
||||
});
|
||||
marker.on("click", function (event) {
|
||||
if (window.rt._exclusionEditMode) return;
|
||||
window.rt.handleInspectorLayerClick(event);
|
||||
});
|
||||
marker.addTo(window.rt._inspectorLayer);
|
||||
|
||||
Reference in New Issue
Block a user