Files
RutasDBUS/RutasDBUS/Components/Pages/PlanificadorRutas.Geocodificacion.cs
Pedro 402ce90a95 - panel de colores puestos en la configuracion
- oscurecido el tb6.
- ordenado el bsucador
- añadido switch de parametros en historial compartido
2026-09-01 08:07:37 +02:00

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}";
}
}