- oscurecido el tb6. - ordenado el bsucador - añadido switch de parametros en historial compartido
316 lines
11 KiB
C#
316 lines
11 KiB
C#
using System.Globalization;
|
|
using RutasDBUS.Modelos.Geocodificacion;
|
|
using RutasDBUS.Modelos.RedBus;
|
|
|
|
namespace RutasDBUS.Components.Pages;
|
|
|
|
public partial class PlanificadorRutas
|
|
{
|
|
private enum CampoBusquedaMapa
|
|
{
|
|
Origen,
|
|
Destino
|
|
}
|
|
|
|
private sealed class SugerenciaBusquedaMapa
|
|
{
|
|
public string Id { get; init; } = "";
|
|
public string TextoEntrada { get; init; } = "";
|
|
public string Titulo { get; init; } = "";
|
|
public string Subtitulo { get; init; } = "";
|
|
public string Tipo { get; init; } = "";
|
|
public string Fuente { get; init; } = "";
|
|
public double Latitud { get; init; }
|
|
public double Longitud { get; init; }
|
|
}
|
|
|
|
private readonly List<SugerenciaBusquedaMapa> _sugerenciasBusquedaOrigen = new();
|
|
private readonly List<SugerenciaBusquedaMapa> _sugerenciasBusquedaDestino = new();
|
|
private CampoBusquedaMapa? _campoBusquedaActivo;
|
|
private SugerenciaBusquedaMapa? _ubicacionOrigenSeleccionada;
|
|
private SugerenciaBusquedaMapa? _ubicacionDestinoSeleccionada;
|
|
private int _versionSugerenciasBusqueda;
|
|
private CancellationTokenSource? _ctsSugerenciasBusqueda;
|
|
|
|
private IReadOnlyList<SugerenciaBusquedaMapa> SugerenciasOrigenVisibles =>
|
|
_campoBusquedaActivo == CampoBusquedaMapa.Origen
|
|
? _sugerenciasBusquedaOrigen
|
|
: Array.Empty<SugerenciaBusquedaMapa>();
|
|
|
|
private IReadOnlyList<SugerenciaBusquedaMapa> SugerenciasDestinoVisibles =>
|
|
_campoBusquedaActivo == CampoBusquedaMapa.Destino
|
|
? _sugerenciasBusquedaDestino
|
|
: Array.Empty<SugerenciaBusquedaMapa>();
|
|
|
|
private Task ActualizarSugerenciasOrigenAsync()
|
|
=> ActualizarSugerenciasBusquedaAsync(CampoBusquedaMapa.Origen);
|
|
|
|
private Task ActualizarSugerenciasDestinoAsync()
|
|
=> ActualizarSugerenciasBusquedaAsync(CampoBusquedaMapa.Destino);
|
|
|
|
private Task MostrarSugerenciasOrigenAsync()
|
|
=> ActualizarSugerenciasBusquedaAsync(CampoBusquedaMapa.Origen);
|
|
|
|
private Task MostrarSugerenciasDestinoAsync()
|
|
=> ActualizarSugerenciasBusquedaAsync(CampoBusquedaMapa.Destino);
|
|
|
|
private async Task ActualizarSugerenciasBusquedaAsync(CampoBusquedaMapa campo)
|
|
{
|
|
_campoBusquedaActivo = campo;
|
|
InvalidarUbicacionSeleccionadaSiEditada(campo);
|
|
|
|
var texto = ObtenerTextoBusqueda(campo);
|
|
var version = ++_versionSugerenciasBusqueda;
|
|
|
|
_ctsSugerenciasBusqueda?.Cancel();
|
|
_ctsSugerenciasBusqueda = new CancellationTokenSource();
|
|
var ct = _ctsSugerenciasBusqueda.Token;
|
|
|
|
if (string.IsNullOrWhiteSpace(texto) || texto.Trim().Length < 2)
|
|
{
|
|
AsignarSugerenciasBusqueda(campo, Array.Empty<SugerenciaBusquedaMapa>());
|
|
StateHasChanged();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
await Task.Delay(120, ct);
|
|
var sugerencias = await CrearSugerenciasBusquedaAsync(texto, 50, ct);
|
|
|
|
if (ct.IsCancellationRequested || version != _versionSugerenciasBusqueda)
|
|
return;
|
|
|
|
AsignarSugerenciasBusqueda(campo, sugerencias);
|
|
StateHasChanged();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
catch
|
|
{
|
|
AsignarSugerenciasBusqueda(campo, Array.Empty<SugerenciaBusquedaMapa>());
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task SeleccionarSugerenciaBusquedaAsync(
|
|
CampoBusquedaMapa campo,
|
|
SugerenciaBusquedaMapa sugerencia)
|
|
{
|
|
if (campo == CampoBusquedaMapa.Origen)
|
|
{
|
|
_textoBusquedaOrigen = sugerencia.TextoEntrada;
|
|
_coordenadasOrigen = new[] { sugerencia.Latitud, sugerencia.Longitud };
|
|
_ubicacionOrigenSeleccionada = sugerencia;
|
|
_sugerenciasBusquedaOrigen.Clear();
|
|
}
|
|
else
|
|
{
|
|
_textoBusquedaDestino = sugerencia.TextoEntrada;
|
|
_coordenadasDestino = new[] { sugerencia.Latitud, sugerencia.Longitud };
|
|
_ubicacionDestinoSeleccionada = sugerencia;
|
|
_sugerenciasBusquedaDestino.Clear();
|
|
}
|
|
|
|
_campoBusquedaActivo = null;
|
|
_mensajeRuta = string.Empty;
|
|
_hayRutaValida = false;
|
|
|
|
await SincronizarMarcadoresOrigenDestinoJsAsync();
|
|
await FitRoutesJsAsync(new List<double[]>
|
|
{
|
|
new[] { sugerencia.Latitud, sugerencia.Longitud }
|
|
});
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task<SugerenciaBusquedaMapa?> ResolverUbicacionBusquedaAsync(
|
|
CampoBusquedaMapa campo,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var texto = ObtenerTextoBusqueda(campo);
|
|
if (string.IsNullOrWhiteSpace(texto))
|
|
return null;
|
|
|
|
var seleccion = ObtenerUbicacionSeleccionada(campo);
|
|
if (seleccion is not null &&
|
|
string.Equals(texto.Trim(), seleccion.TextoEntrada, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return seleccion;
|
|
}
|
|
|
|
var parada = ResolverParadaDesdeTexto(texto);
|
|
if (parada is not null)
|
|
return CrearSugerenciaParada(parada);
|
|
|
|
var sugerencias = await CrearSugerenciasBusquedaAsync(texto, 1, cancellationToken);
|
|
return sugerencias.FirstOrDefault();
|
|
}
|
|
|
|
private async Task<IReadOnlyList<SugerenciaBusquedaMapa>> CrearSugerenciasBusquedaAsync(
|
|
string texto,
|
|
int maxResultados,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var paradas = BuscarSugerenciasParadas(texto, 5);
|
|
var lugares = await GeocodificadorLocal.BuscarAsync(texto, Math.Max(6, maxResultados), cancellationToken);
|
|
|
|
return paradas
|
|
.Concat(lugares.Select(CrearSugerenciaLugar))
|
|
.GroupBy(sugerencia => NormalizarClaveSugerencia(sugerencia), StringComparer.Ordinal)
|
|
.Select(grupo => grupo.First())
|
|
.OrderBy(sugerencia => OrdenTipoSugerencia(sugerencia.Tipo))
|
|
.ThenBy(sugerencia => sugerencia.Titulo, StringComparer.CurrentCultureIgnoreCase)
|
|
.ThenBy(sugerencia => sugerencia.Subtitulo, StringComparer.CurrentCultureIgnoreCase)
|
|
.Take(Math.Clamp(maxResultados, 1, 50))
|
|
.ToList();
|
|
}
|
|
|
|
private static int OrdenTipoSugerencia(string? tipo)
|
|
=> tipo switch
|
|
{
|
|
"Parada" => 0,
|
|
"Direccion" => 1,
|
|
"Lugar" => 2,
|
|
_ => 3
|
|
};
|
|
|
|
private IReadOnlyList<SugerenciaBusquedaMapa> BuscarSugerenciasParadas(string texto, int maxResultados)
|
|
{
|
|
var consulta = NormalizeForSearch(texto);
|
|
var tokens = consulta
|
|
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
|
|
if (tokens.Length == 0)
|
|
return Array.Empty<SugerenciaBusquedaMapa>();
|
|
|
|
return RedBusServicio.Paradas
|
|
.Select(parada => new
|
|
{
|
|
Parada = parada,
|
|
Puntuacion = PuntuarParada(parada, consulta, tokens)
|
|
})
|
|
.Where(x => x.Puntuacion < int.MaxValue)
|
|
.OrderBy(x => x.Puntuacion)
|
|
.ThenBy(x => x.Parada.Nombre, StringComparer.CurrentCultureIgnoreCase)
|
|
.Take(Math.Clamp(maxResultados, 1, 12))
|
|
.Select(x => CrearSugerenciaParada(x.Parada))
|
|
.ToList();
|
|
}
|
|
|
|
private static int PuntuarParada(Parada parada, string consulta, IReadOnlyList<string> tokens)
|
|
{
|
|
var codigo = NormalizeForSearch(CodigoParadaFormateado(parada.Codigo));
|
|
var codigoRaw = NormalizeForSearch(parada.Codigo);
|
|
var nombre = NormalizeForSearch(parada.Nombre);
|
|
var nombreCorto = NormalizeForSearch(parada.NombreCorto);
|
|
var abreviatura = NormalizeForSearch(parada.Abreviatura);
|
|
var texto = $"{codigo} {codigoRaw} {nombre} {nombreCorto} {abreviatura}";
|
|
|
|
foreach (var token in tokens)
|
|
{
|
|
if (!texto.Contains(token, StringComparison.Ordinal))
|
|
return int.MaxValue;
|
|
}
|
|
|
|
if (codigo.Equals(consulta, StringComparison.Ordinal) ||
|
|
codigoRaw.Equals(consulta, StringComparison.Ordinal))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (nombre.Equals(consulta, StringComparison.Ordinal))
|
|
return 8;
|
|
|
|
if (nombre.StartsWith(consulta, StringComparison.Ordinal) ||
|
|
nombreCorto.StartsWith(consulta, StringComparison.Ordinal) ||
|
|
abreviatura.StartsWith(consulta, StringComparison.Ordinal))
|
|
{
|
|
return 15;
|
|
}
|
|
|
|
return 45 + Math.Min(40, nombre.Length / 3);
|
|
}
|
|
|
|
private static SugerenciaBusquedaMapa CrearSugerenciaParada(Parada parada)
|
|
{
|
|
var codigo = CodigoParadaFormateado(parada.Codigo);
|
|
var texto = $"{parada.Nombre} ({codigo})";
|
|
|
|
return new SugerenciaBusquedaMapa
|
|
{
|
|
Id = "parada:" + parada.Codigo,
|
|
TextoEntrada = texto,
|
|
Titulo = texto,
|
|
Subtitulo = "Parada DBUS",
|
|
Tipo = "Parada",
|
|
Fuente = "DBUS",
|
|
Latitud = parada.Latitud,
|
|
Longitud = parada.Longitud
|
|
};
|
|
}
|
|
|
|
private static SugerenciaBusquedaMapa CrearSugerenciaLugar(LugarGeocodificado lugar)
|
|
{
|
|
return new SugerenciaBusquedaMapa
|
|
{
|
|
Id = lugar.Id,
|
|
TextoEntrada = lugar.Nombre,
|
|
Titulo = lugar.Nombre,
|
|
Subtitulo = string.IsNullOrWhiteSpace(lugar.Subtitulo)
|
|
? lugar.Tipo
|
|
: lugar.Subtitulo,
|
|
Tipo = lugar.Tipo,
|
|
Fuente = lugar.Fuente,
|
|
Latitud = lugar.Latitud,
|
|
Longitud = lugar.Longitud
|
|
};
|
|
}
|
|
|
|
private string ObtenerTextoBusqueda(CampoBusquedaMapa campo)
|
|
=> campo == CampoBusquedaMapa.Origen
|
|
? _textoBusquedaOrigen
|
|
: _textoBusquedaDestino;
|
|
|
|
private SugerenciaBusquedaMapa? ObtenerUbicacionSeleccionada(CampoBusquedaMapa campo)
|
|
=> campo == CampoBusquedaMapa.Origen
|
|
? _ubicacionOrigenSeleccionada
|
|
: _ubicacionDestinoSeleccionada;
|
|
|
|
private void InvalidarUbicacionSeleccionadaSiEditada(CampoBusquedaMapa campo)
|
|
{
|
|
var texto = ObtenerTextoBusqueda(campo).Trim();
|
|
var seleccion = ObtenerUbicacionSeleccionada(campo);
|
|
if (seleccion is null ||
|
|
string.Equals(texto, seleccion.TextoEntrada, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (campo == CampoBusquedaMapa.Origen)
|
|
_ubicacionOrigenSeleccionada = null;
|
|
else
|
|
_ubicacionDestinoSeleccionada = null;
|
|
}
|
|
|
|
private void AsignarSugerenciasBusqueda(
|
|
CampoBusquedaMapa campo,
|
|
IReadOnlyList<SugerenciaBusquedaMapa> sugerencias)
|
|
{
|
|
var lista = campo == CampoBusquedaMapa.Origen
|
|
? _sugerenciasBusquedaOrigen
|
|
: _sugerenciasBusquedaDestino;
|
|
|
|
lista.Clear();
|
|
lista.AddRange(sugerencias);
|
|
}
|
|
|
|
private static string NormalizarClaveSugerencia(SugerenciaBusquedaMapa sugerencia)
|
|
{
|
|
var lat = sugerencia.Latitud.ToString("0.000000", CultureInfo.InvariantCulture);
|
|
var lon = sugerencia.Longitud.ToString("0.000000", CultureInfo.InvariantCulture);
|
|
return $"{NormalizeForSearch(sugerencia.Titulo)}|{lat}|{lon}";
|
|
}
|
|
}
|