Files
RutasDBUS/RutasDBUS/Servicios/Caminata/ClienteAtributosOsm.cs
Pedro 7090dfa9be v-20260916a
- restaurar derivaciones
- añadido la pedida de anchura de calles y cariiles
- correcion paradas bus
- desglose del score
- historial no cambia la hora
- resuarar no cambia modo de historial
2026-09-16 13:26:13 +02:00

137 lines
4.6 KiB
C#

using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Caching.Memory;
namespace RutasDBUS.Servicios.Caminata;
public sealed class ClienteAtributosOsm : IClienteAtributosOsm
{
private const int MaximoCaminosPorConsulta = 100;
private readonly HttpClient _httpClient;
private readonly IMemoryCache _cache;
private readonly string _baseUrl;
public ClienteAtributosOsm(
HttpClient httpClient,
IMemoryCache cache,
IConfiguration configuracion)
{
_httpClient = httpClient;
_cache = cache;
_baseUrl = (configuracion["Caminata:Osm:BaseUrl"]
?? "https://api.openstreetmap.org/api/0.6").Trim().TrimEnd('/');
}
public async Task<IReadOnlyDictionary<long, AnchuraViaOsm>> ObtenerAnchurasAsync(
IEnumerable<long> idsCamino,
CancellationToken cancellationToken = default)
{
var ids = idsCamino
.Where(id => id > 0)
.Distinct()
.Take(MaximoCaminosPorConsulta)
.ToList();
var resultado = new Dictionary<long, AnchuraViaOsm>();
var pendientes = new List<long>();
foreach (var id in ids)
{
if (_cache.TryGetValue<ResultadoAnchuraCache>(ClaveCache(id), out var cacheado))
{
if (cacheado?.Dato is not null)
resultado[id] = cacheado.Dato;
}
else
{
pendientes.Add(id);
}
}
if (pendientes.Count == 0)
return resultado;
try
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeout.CancelAfter(TimeSpan.FromSeconds(4));
var idsConsulta = string.Join(',', pendientes);
using var peticion = new HttpRequestMessage(
HttpMethod.Get,
$"{_baseUrl}/ways.json?ways={Uri.EscapeDataString(idsConsulta)}");
peticion.Headers.UserAgent.ParseAdd("RutasDBUS/1.0");
using var respuesta = await _httpClient.SendAsync(peticion, timeout.Token);
if (!respuesta.IsSuccessStatusCode)
return resultado;
var datos = await respuesta.Content.ReadFromJsonAsync<RespuestaOsm>(timeout.Token);
var encontrados = new HashSet<long>();
foreach (var elemento in datos?.Elementos ?? [])
{
encontrados.Add(elemento.Id);
var anchura = ObtenerAnchura(elemento.Etiquetas);
GuardarCache(elemento.Id, anchura);
if (anchura is not null)
resultado[elemento.Id] = anchura;
}
foreach (var id in pendientes.Where(id => !encontrados.Contains(id)))
GuardarCache(id, null);
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
// La anchura es informativa: un timeout no debe bloquear el tooltip.
}
catch (HttpRequestException)
{
// La información principal de v2 sigue siendo válida si OSM no responde.
}
catch (System.Text.Json.JsonException)
{
// Una respuesta no válida tampoco debe ocultar la información principal de v2.
}
return resultado;
}
private void GuardarCache(long id, AnchuraViaOsm? dato)
=> _cache.Set(
ClaveCache(id),
new ResultadoAnchuraCache(dato),
dato is null ? TimeSpan.FromHours(6) : TimeSpan.FromDays(1));
private static AnchuraViaOsm? ObtenerAnchura(Dictionary<string, string>? etiquetas)
{
if (etiquetas is null)
return null;
if (etiquetas.TryGetValue("width", out var anchura) && !string.IsNullOrWhiteSpace(anchura))
return new AnchuraViaOsm(anchura.Trim(), false);
if (etiquetas.TryGetValue("est_width", out var estimada) && !string.IsNullOrWhiteSpace(estimada))
return new AnchuraViaOsm(estimada.Trim(), true);
return null;
}
private static string ClaveCache(long id) => $"osm:anchura:{id}";
private sealed record ResultadoAnchuraCache(AnchuraViaOsm? Dato);
private sealed class RespuestaOsm
{
[JsonPropertyName("elements")]
public List<ElementoOsm>? Elementos { get; set; }
}
private sealed class ElementoOsm
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("tags")]
public Dictionary<string, string>? Etiquetas { get; set; }
}
}