Initial clean RutasDBUS app
This commit is contained in:
275
RutasDBUS/Servicios/Caminata/ClienteValhalla.cs
Normal file
275
RutasDBUS/Servicios/Caminata/ClienteValhalla.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace RutasDBUS.Servicios.Caminata;
|
||||
|
||||
public class ClienteValhalla : IClienteValhalla
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
/// <summary>
|
||||
/// Inicializa una nueva instancia de la clase ClienteValhalla.
|
||||
/// </summary>
|
||||
public ClienteValhalla(HttpClient httpClient, IConfiguration configuracion)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
|
||||
_baseUrl = configuracion["Caminata:Valhalla:BaseUrl"]
|
||||
?? throw new InvalidOperationException(
|
||||
"Falta la configuración 'Caminata:Valhalla:BaseUrl' en appsettings.json.");
|
||||
|
||||
_baseUrl = _baseUrl.Trim().TrimEnd('/');
|
||||
}
|
||||
|
||||
public async Task<(double[,]? Linea, double DistanciaMetros, double DuracionSegundos)> ObtenerRutaAsync(
|
||||
double latitudDesde,
|
||||
double longitudDesde,
|
||||
double latitudHasta,
|
||||
double longitudHasta,
|
||||
string costing = "pedestrian")
|
||||
{
|
||||
var cuerpo = new SolicitudValhalla
|
||||
{
|
||||
Ubicaciones = new List<LocalizacionValhalla>
|
||||
{
|
||||
new() { Latitud = latitudDesde, Longitud = longitudDesde },
|
||||
new() { Latitud = latitudHasta, Longitud = longitudHasta }
|
||||
},
|
||||
PerfilCoste = string.IsNullOrWhiteSpace(costing) ? "pedestrian" : costing,
|
||||
FormatoGeometria = "polyline6",
|
||||
OpcionesIndicaciones = new OpcionesIndicacionesValhalla
|
||||
{
|
||||
Idioma = "es-ES",
|
||||
Unidades = "kilometers"
|
||||
}
|
||||
};
|
||||
|
||||
HttpResponseMessage respuesta;
|
||||
try
|
||||
{
|
||||
respuesta = await _httpClient.PostAsJsonAsync($"{_baseUrl}/route", cuerpo);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (null, 0, 0);
|
||||
}
|
||||
|
||||
if (!respuesta.IsSuccessStatusCode)
|
||||
return (null, 0, 0);
|
||||
|
||||
RespuestaRutaValhalla? data;
|
||||
try
|
||||
{
|
||||
data = await respuesta.Content.ReadFromJsonAsync<RespuestaRutaValhalla>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (null, 0, 0);
|
||||
}
|
||||
|
||||
var viaje = data?.Viaje;
|
||||
if (viaje is null || viaje.Tramos is null || viaje.Tramos.Count == 0)
|
||||
return (null, 0, 0);
|
||||
|
||||
var forma = viaje.Tramos[0]?.Forma;
|
||||
if (string.IsNullOrWhiteSpace(forma))
|
||||
return (null, 0, 0);
|
||||
|
||||
var coords = DecodificarPolyline6(forma);
|
||||
if (coords.Count == 0)
|
||||
return (null, 0, 0);
|
||||
|
||||
var linea = new double[coords.Count, 2];
|
||||
for (int i = 0; i < coords.Count; i++)
|
||||
{
|
||||
linea[i, 0] = coords[i].Latitud;
|
||||
linea[i, 1] = coords[i].Longitud;
|
||||
}
|
||||
|
||||
// summary.length depende de units
|
||||
double longitud = viaje.Resumen?.Longitud ?? 0.0;
|
||||
string unidades = (viaje.Unidades ?? "").Trim().ToLowerInvariant();
|
||||
|
||||
double distanciaMetros = unidades switch
|
||||
{
|
||||
"miles" => longitud * 1609.344,
|
||||
_ => longitud * 1000.0 // "kilometers" o desconocido -> km
|
||||
};
|
||||
|
||||
double duracionSegundos = viaje.Resumen?.Tiempo ?? 0.0;
|
||||
|
||||
return (
|
||||
AnclarExtremosLinea(linea, latitudDesde, longitudDesde, latitudHasta, longitudHasta),
|
||||
distanciaMetros,
|
||||
duracionSegundos);
|
||||
}
|
||||
|
||||
// =======================
|
||||
// Modelos request/response
|
||||
// =======================
|
||||
|
||||
private sealed class SolicitudValhalla
|
||||
{
|
||||
[JsonPropertyName("locations")]
|
||||
public List<LocalizacionValhalla> Ubicaciones { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("costing")]
|
||||
public string PerfilCoste { get; set; } = "pedestrian";
|
||||
|
||||
[JsonPropertyName("shape_format")]
|
||||
public string FormatoGeometria { get; set; } = "polyline6";
|
||||
|
||||
[JsonPropertyName("directions_options")]
|
||||
public OpcionesIndicacionesValhalla OpcionesIndicaciones { get; set; } = new();
|
||||
}
|
||||
|
||||
private sealed class LocalizacionValhalla
|
||||
{
|
||||
[JsonPropertyName("lat")]
|
||||
public double Latitud { get; set; }
|
||||
|
||||
[JsonPropertyName("lon")]
|
||||
public double Longitud { get; set; }
|
||||
}
|
||||
|
||||
private sealed class OpcionesIndicacionesValhalla
|
||||
{
|
||||
[JsonPropertyName("language")]
|
||||
public string Idioma { get; set; } = "es-ES";
|
||||
|
||||
[JsonPropertyName("units")]
|
||||
public string Unidades { get; set; } = "kilometers";
|
||||
}
|
||||
|
||||
private sealed class RespuestaRutaValhalla
|
||||
{
|
||||
[JsonPropertyName("trip")]
|
||||
public ViajeValhalla? Viaje { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ViajeValhalla
|
||||
{
|
||||
[JsonPropertyName("summary")]
|
||||
public ResumenValhalla? Resumen { get; set; }
|
||||
|
||||
[JsonPropertyName("legs")]
|
||||
public List<TramoValhalla?> Tramos { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("units")]
|
||||
public string? Unidades { get; set; } // "kilometers" / "miles"
|
||||
}
|
||||
|
||||
private sealed class ResumenValhalla
|
||||
{
|
||||
[JsonPropertyName("length")]
|
||||
public double Longitud { get; set; } // en km si units=kilometers
|
||||
|
||||
[JsonPropertyName("time")]
|
||||
public double Tiempo { get; set; } // segundos
|
||||
}
|
||||
|
||||
private sealed class TramoValhalla
|
||||
{
|
||||
[JsonPropertyName("shape")]
|
||||
public string? Forma { get; set; } // polyline6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private readonly record struct Coordenada(double Latitud, double Longitud);
|
||||
|
||||
/// <summary>
|
||||
/// Decodifica polyline6 (factor 1e6).
|
||||
/// </summary>
|
||||
private static List<Coordenada> DecodificarPolyline6(string encoded)
|
||||
{
|
||||
var coords = new List<Coordenada>();
|
||||
if (string.IsNullOrEmpty(encoded)) return coords;
|
||||
|
||||
int index = 0;
|
||||
int lat = 0, lon = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (index < encoded.Length)
|
||||
{
|
||||
lat += LeerSiguienteValor(encoded, ref index);
|
||||
lon += LeerSiguienteValor(encoded, ref index);
|
||||
|
||||
coords.Add(new Coordenada(lat / 1e6, lon / 1e6));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// si llega corrupto o incompleto, devolvemos lo que haya (o vacío)
|
||||
if (coords.Count == 0) return new List<Coordenada>();
|
||||
}
|
||||
|
||||
return coords;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lee siguiente valor.
|
||||
/// </summary>
|
||||
private static int LeerSiguienteValor(string encoded, ref int index)
|
||||
{
|
||||
int result = 0;
|
||||
int shift = 0;
|
||||
int b;
|
||||
|
||||
do
|
||||
{
|
||||
if (index >= encoded.Length) throw new InvalidOperationException("Polyline incompleta.");
|
||||
b = encoded[index++] - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
}
|
||||
while (b >= 0x20);
|
||||
|
||||
// zigzag decode
|
||||
return (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuerza que la geometria empiece y termine exactamente en los puntos solicitados.
|
||||
/// </summary>
|
||||
private static double[,] AnclarExtremosLinea(
|
||||
double[,] linea,
|
||||
double latitudDesde,
|
||||
double longitudDesde,
|
||||
double latitudHasta,
|
||||
double longitudHasta)
|
||||
{
|
||||
var puntos = linea.GetLength(0);
|
||||
if (puntos <= 0)
|
||||
{
|
||||
return new double[,]
|
||||
{
|
||||
{ latitudDesde, longitudDesde },
|
||||
{ latitudHasta, longitudHasta }
|
||||
};
|
||||
}
|
||||
|
||||
if (puntos == 1)
|
||||
{
|
||||
return new double[,]
|
||||
{
|
||||
{ latitudDesde, longitudDesde },
|
||||
{ latitudHasta, longitudHasta }
|
||||
};
|
||||
}
|
||||
|
||||
linea[0, 0] = latitudDesde;
|
||||
linea[0, 1] = longitudDesde;
|
||||
linea[puntos - 1, 0] = latitudHasta;
|
||||
linea[puntos - 1, 1] = longitudHasta;
|
||||
return linea;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user