- andar equilibraod separado del calculo
- comienzo de penalizacoin en 200m - centrado de origen
This commit is contained in:
@@ -48,6 +48,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
// CONFIG
|
||||
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
|
||||
private const double ParametroPenalizacionDistanciaPieMetrosPorDefecto = 200.0;
|
||||
private const double CoeficienteEsfuerzoAlturaPorDefecto = 8.2;
|
||||
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
|
||||
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
|
||||
@@ -56,12 +57,12 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
private double _velocidadPieKmH = 3.6;
|
||||
private double _factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
|
||||
private double _parametroPenalizacionDistanciaPieMetros = 100.0;
|
||||
private double _parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||
private double _pesoTransbordoRankingMinutos = 4.0;
|
||||
private double _factorEquilibradoAndarBus = 12.0;
|
||||
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
|
||||
private string _parametroPenalizacionDistanciaPieTextoEdicion = "100";
|
||||
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
|
||||
private string _coeficienteEsfuerzoAlturaTextoEdicion = "8.20";
|
||||
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
||||
private string _factorEquilibradoAndarBusTextoEdicion = "12";
|
||||
@@ -461,21 +462,47 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Construye paradas for variante.
|
||||
/// Construye el payload JS de paradas de una variante.
|
||||
/// </summary>
|
||||
private static List<object> BuildStopsDesdeParadas(IEnumerable<Parada> paradas)
|
||||
private List<object> BuildStopsForVariante(InfoVariante v)
|
||||
{
|
||||
var paradasOrdenadas = paradas.ToList();
|
||||
var stops = new List<object>(paradasOrdenadas.Count);
|
||||
for (var indice = 0; indice < paradasOrdenadas.Count; indice++)
|
||||
if (v?.CodigosParadas is null || v.CodigosParadas.Count == 0)
|
||||
return new List<object>();
|
||||
|
||||
return BuildStopsParaRangoVariante(v, 0, v.CodigosParadas.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye el payload JS de paradas recorridas por un tramo concreto.
|
||||
/// </summary>
|
||||
private List<object> BuildStopsForTramoBus(TramoBus tramo)
|
||||
{
|
||||
if (tramo?.Variante?.CodigosParadas is null || tramo.Variante.CodigosParadas.Count == 0)
|
||||
return new List<object>();
|
||||
|
||||
var ultimoIndice = tramo.Variante.CodigosParadas.Count - 1;
|
||||
var inicio = Math.Clamp(Math.Min(tramo.IndiceInicio, tramo.IndiceFin), 0, ultimoIndice);
|
||||
var fin = Math.Clamp(Math.Max(tramo.IndiceInicio, tramo.IndiceFin), 0, ultimoIndice);
|
||||
return BuildStopsParaRangoVariante(tramo.Variante, inicio, fin);
|
||||
}
|
||||
|
||||
private List<object> BuildStopsParaRangoVariante(InfoVariante variante, int inicio, int fin)
|
||||
{
|
||||
var stops = new List<object>();
|
||||
var codigosVistos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var indice = inicio; indice <= fin; indice++)
|
||||
{
|
||||
var parada = paradasOrdenadas[indice];
|
||||
var codigo = variante.CodigosParadas[indice];
|
||||
if (!codigosVistos.Add(codigo) || !_paradaByCode.TryGetValue(codigo, out var parada))
|
||||
continue;
|
||||
|
||||
stops.Add(new
|
||||
{
|
||||
lat = parada.Latitud,
|
||||
lon = parada.Longitud,
|
||||
code = parada.Codigo,
|
||||
bearing = CalcularRumboParada(paradasOrdenadas, indice)
|
||||
bearing = CalcularRumboParada(variante, indice)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -483,21 +510,121 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula el sentido de marcha hacia la siguiente parada; en la terminal conserva el rumbo de llegada.
|
||||
/// Obtiene la tangente local de la geometria real al salir de la parada.
|
||||
/// En la terminal usa la tangente de llegada del ultimo trayecto.
|
||||
/// </summary>
|
||||
private static double? CalcularRumboParada(IReadOnlyList<Parada> paradas, int indice)
|
||||
private double? CalcularRumboParada(InfoVariante variante, int indiceParada)
|
||||
{
|
||||
var actual = paradas[indice];
|
||||
for (var siguiente = indice + 1; siguiente < paradas.Count; siguiente++)
|
||||
var idsTrayectos = variante.Variante?.IdsTrayectos;
|
||||
if (idsTrayectos is not null)
|
||||
{
|
||||
var rumbo = CalcularRumboGrados(actual, paradas[siguiente]);
|
||||
if (indiceParada >= 0 &&
|
||||
indiceParada < idsTrayectos.Count &&
|
||||
RedBusServicio.Trayectos.TryGetValue(idsTrayectos[indiceParada], out var trayectoSalida))
|
||||
{
|
||||
var rumboSalida = CalcularRumboGeometriaTrayecto(trayectoSalida.Posiciones, desdeInicio: true);
|
||||
if (rumboSalida is not null)
|
||||
return rumboSalida;
|
||||
}
|
||||
|
||||
var indiceTrayectoLlegada = indiceParada - 1;
|
||||
if (indiceTrayectoLlegada >= 0 &&
|
||||
indiceTrayectoLlegada < idsTrayectos.Count &&
|
||||
RedBusServicio.Trayectos.TryGetValue(idsTrayectos[indiceTrayectoLlegada], out var trayectoLlegada))
|
||||
{
|
||||
var rumboLlegada = CalcularRumboGeometriaTrayecto(trayectoLlegada.Posiciones, desdeInicio: false);
|
||||
if (rumboLlegada is not null)
|
||||
return rumboLlegada;
|
||||
}
|
||||
}
|
||||
|
||||
return CalcularRumboParadaPorCoordenadas(variante, indiceParada);
|
||||
}
|
||||
|
||||
private static double? CalcularRumboGeometriaTrayecto(
|
||||
IReadOnlyList<double[]> posiciones,
|
||||
bool desdeInicio)
|
||||
{
|
||||
var puntos = posiciones
|
||||
.Where(punto =>
|
||||
punto is { Length: >= 2 } &&
|
||||
double.IsFinite(punto[0]) &&
|
||||
double.IsFinite(punto[1]))
|
||||
.ToList();
|
||||
|
||||
if (puntos.Count < 2)
|
||||
return null;
|
||||
|
||||
const double distanciaMinimaRumboMetros = 3.0;
|
||||
double? rumboCorto = null;
|
||||
|
||||
if (desdeInicio)
|
||||
{
|
||||
var origen = puntos[0];
|
||||
for (var indice = 1; indice < puntos.Count; indice++)
|
||||
{
|
||||
var destino = puntos[indice];
|
||||
var rumbo = CalcularRumboGrados(origen[0], origen[1], destino[0], destino[1]);
|
||||
if (rumbo is null)
|
||||
continue;
|
||||
|
||||
rumboCorto ??= rumbo;
|
||||
if (CalcularDistanciaMetros(origen[0], origen[1], destino[0], destino[1]) >= distanciaMinimaRumboMetros)
|
||||
return rumbo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var destino = puntos[^1];
|
||||
for (var indice = puntos.Count - 2; indice >= 0; indice--)
|
||||
{
|
||||
var origen = puntos[indice];
|
||||
var rumbo = CalcularRumboGrados(origen[0], origen[1], destino[0], destino[1]);
|
||||
if (rumbo is null)
|
||||
continue;
|
||||
|
||||
rumboCorto ??= rumbo;
|
||||
if (CalcularDistanciaMetros(origen[0], origen[1], destino[0], destino[1]) >= distanciaMinimaRumboMetros)
|
||||
return rumbo;
|
||||
}
|
||||
}
|
||||
|
||||
return rumboCorto;
|
||||
}
|
||||
|
||||
private double? CalcularRumboParadaPorCoordenadas(InfoVariante variante, int indice)
|
||||
{
|
||||
if (indice < 0 ||
|
||||
indice >= variante.CodigosParadas.Count ||
|
||||
!_paradaByCode.TryGetValue(variante.CodigosParadas[indice], out var actual))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var siguiente = indice + 1; siguiente < variante.CodigosParadas.Count; siguiente++)
|
||||
{
|
||||
if (!_paradaByCode.TryGetValue(variante.CodigosParadas[siguiente], out var paradaSiguiente))
|
||||
continue;
|
||||
|
||||
var rumbo = CalcularRumboGrados(
|
||||
actual.Latitud,
|
||||
actual.Longitud,
|
||||
paradaSiguiente.Latitud,
|
||||
paradaSiguiente.Longitud);
|
||||
if (rumbo is not null)
|
||||
return rumbo;
|
||||
}
|
||||
|
||||
for (var anterior = indice - 1; anterior >= 0; anterior--)
|
||||
{
|
||||
var rumbo = CalcularRumboGrados(paradas[anterior], actual);
|
||||
if (!_paradaByCode.TryGetValue(variante.CodigosParadas[anterior], out var paradaAnterior))
|
||||
continue;
|
||||
|
||||
var rumbo = CalcularRumboGrados(
|
||||
paradaAnterior.Latitud,
|
||||
paradaAnterior.Longitud,
|
||||
actual.Latitud,
|
||||
actual.Longitud);
|
||||
if (rumbo is not null)
|
||||
return rumbo;
|
||||
}
|
||||
@@ -505,19 +632,23 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return null;
|
||||
}
|
||||
|
||||
private static double? CalcularRumboGrados(Parada origen, Parada destino)
|
||||
private static double? CalcularRumboGrados(
|
||||
double latitudOrigenGrados,
|
||||
double longitudOrigenGrados,
|
||||
double latitudDestinoGrados,
|
||||
double longitudDestinoGrados)
|
||||
{
|
||||
if (!double.IsFinite(origen.Latitud) ||
|
||||
!double.IsFinite(origen.Longitud) ||
|
||||
!double.IsFinite(destino.Latitud) ||
|
||||
!double.IsFinite(destino.Longitud))
|
||||
if (!double.IsFinite(latitudOrigenGrados) ||
|
||||
!double.IsFinite(longitudOrigenGrados) ||
|
||||
!double.IsFinite(latitudDestinoGrados) ||
|
||||
!double.IsFinite(longitudDestinoGrados))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var latitudOrigen = origen.Latitud * Math.PI / 180.0;
|
||||
var latitudDestino = destino.Latitud * Math.PI / 180.0;
|
||||
var diferenciaLongitud = (destino.Longitud - origen.Longitud) * Math.PI / 180.0;
|
||||
var latitudOrigen = latitudOrigenGrados * Math.PI / 180.0;
|
||||
var latitudDestino = latitudDestinoGrados * Math.PI / 180.0;
|
||||
var diferenciaLongitud = (longitudDestinoGrados - longitudOrigenGrados) * Math.PI / 180.0;
|
||||
var y = Math.Sin(diferenciaLongitud) * Math.Cos(latitudDestino);
|
||||
var x = Math.Cos(latitudOrigen) * Math.Sin(latitudDestino) -
|
||||
Math.Sin(latitudOrigen) * Math.Cos(latitudDestino) * Math.Cos(diferenciaLongitud);
|
||||
@@ -529,33 +660,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return (rumbo + 360.0) % 360.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye las paradas visibles recorridas por una variante completa.
|
||||
/// </summary>
|
||||
private List<Parada> BuildParadasForVariante(InfoVariante v)
|
||||
{
|
||||
var stops = new List<Parada>(v?.CodigosParadas?.Count ?? 0);
|
||||
if (v?.CodigosParadas is null) return stops;
|
||||
|
||||
var codigosVistos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var cod in v.CodigosParadas)
|
||||
{
|
||||
if (!codigosVistos.Add(cod))
|
||||
continue;
|
||||
|
||||
if (_paradaByCode.TryGetValue(cod, out var parada))
|
||||
stops.Add(parada);
|
||||
}
|
||||
|
||||
return stops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye el payload JS de paradas de una variante.
|
||||
/// </summary>
|
||||
private List<object> BuildStopsForVariante(InfoVariante v)
|
||||
=> BuildStopsDesdeParadas(BuildParadasForVariante(v));
|
||||
|
||||
/// <summary>
|
||||
/// Construye las paradas visibles recorridas por un tramo concreto de bus.
|
||||
/// </summary>
|
||||
@@ -1061,14 +1165,16 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
/// <summary>
|
||||
/// Ajusta rutas js.
|
||||
/// </summary>
|
||||
private async Task FitRoutesJsAsync(IReadOnlyList<double[]>? coordenadas = null)
|
||||
private async Task FitRoutesJsAsync(
|
||||
IReadOnlyList<double[]>? coordenadas = null,
|
||||
double[]? centroPreferido = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (coordenadas is null)
|
||||
if (coordenadas is null && centroPreferido is null)
|
||||
await JS.InvokeVoidAsync("rt.fitRoutes");
|
||||
else
|
||||
await JS.InvokeVoidAsync("rt.fitRoutes", coordenadas);
|
||||
await JS.InvokeVoidAsync("rt.fitRoutes", coordenadas, centroPreferido);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -1267,11 +1373,19 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
var parametros = CrearParametrosPenalizacionActuales();
|
||||
|
||||
var distanciaCorregidaBase = alternativa.EsSoloAPie
|
||||
? UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaSoloAPie, parametros)
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieInicioAprox, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieTransbordosAprox, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieFinAprox, parametros);
|
||||
IEnumerable<double> distanciasPiePorTramo = alternativa.EsSoloAPie
|
||||
? new[] { alternativa.DistanciaSoloAPie }
|
||||
: alternativa.DistanciasPiePorTramoAprox.Count > 0
|
||||
? alternativa.DistanciasPiePorTramoAprox
|
||||
: new[]
|
||||
{
|
||||
alternativa.DistanciaPieInicioAprox,
|
||||
alternativa.DistanciaPieTransbordosAprox,
|
||||
alternativa.DistanciaPieFinAprox
|
||||
};
|
||||
var distanciaCorregidaBase = UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaTotalMetros(
|
||||
distanciasPiePorTramo,
|
||||
parametros);
|
||||
var esfuerzoExtra = double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
||||
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
|
||||
: 0;
|
||||
@@ -1391,12 +1505,19 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
alternativa.DistanciaSoloAPie);
|
||||
}
|
||||
|
||||
var distanciasPiePorTramo = alternativa.DistanciasPiePorTramoAprox.Count > 0
|
||||
? alternativa.DistanciasPiePorTramoAprox.ToArray()
|
||||
: new[]
|
||||
{
|
||||
alternativa.DistanciaPieInicioAprox,
|
||||
alternativa.DistanciaPieTransbordosAprox,
|
||||
alternativa.DistanciaPieFinAprox
|
||||
};
|
||||
|
||||
return UtilidadesTransitoDbus.CalcularCosteConPenalizacionPieSegundos(
|
||||
duracionReal,
|
||||
parametros,
|
||||
alternativa.DistanciaPieInicioAprox,
|
||||
alternativa.DistanciaPieTransbordosAprox,
|
||||
alternativa.DistanciaPieFinAprox);
|
||||
distanciasPiePorTramo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -432,16 +432,29 @@ public partial class PlanificadorRutas
|
||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||
return;
|
||||
|
||||
var item = _historialLocal.FirstOrDefault(x => x.Id == id);
|
||||
if (item is null)
|
||||
var indiceEliminado = _historialLocal.FindIndex(x => x.Id == id);
|
||||
if (indiceEliminado < 0)
|
||||
return;
|
||||
|
||||
_historialLocal.Remove(item);
|
||||
|
||||
if (_historialLocalSeleccionadoId == id)
|
||||
_historialLocalSeleccionadoId = string.Empty;
|
||||
var estabaSeleccionado = _historialLocalSeleccionadoId == id;
|
||||
_historialLocal.RemoveAt(indiceEliminado);
|
||||
|
||||
await PersistirHistorialLocalAsync();
|
||||
|
||||
if (estabaSeleccionado)
|
||||
{
|
||||
if (_historialLocal.Count == 0)
|
||||
{
|
||||
_historialLocalSeleccionadoId = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var siguiente = _historialLocal[Math.Min(indiceEliminado, _historialLocal.Count - 1)];
|
||||
await AplicarRegistroHistorialAsync(siguiente);
|
||||
SeleccionarHistorialLocal(siguiente.Id);
|
||||
}
|
||||
}
|
||||
|
||||
MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
|
||||
}
|
||||
|
||||
@@ -473,6 +486,8 @@ public partial class PlanificadorRutas
|
||||
{
|
||||
try
|
||||
{
|
||||
var indiceEliminado = _historialCompartido.FindIndex(x => x.Id == id);
|
||||
var estabaSeleccionado = _historialCompartidoSeleccionadoId == id;
|
||||
var ok = await HistorialRutasCompartidoService.EliminarAsync(id);
|
||||
if (!ok)
|
||||
{
|
||||
@@ -481,9 +496,22 @@ public partial class PlanificadorRutas
|
||||
}
|
||||
|
||||
await CargarHistorialCompartidoAsync();
|
||||
_historialCompartido.RemoveAll(x => x.Id == id);
|
||||
|
||||
if (_historialCompartidoSeleccionadoId == id)
|
||||
_historialCompartidoSeleccionadoId = string.Empty;
|
||||
if (estabaSeleccionado)
|
||||
{
|
||||
if (_historialCompartido.Count == 0)
|
||||
{
|
||||
_historialCompartidoSeleccionadoId = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var indiceSiguiente = Math.Clamp(indiceEliminado, 0, _historialCompartido.Count - 1);
|
||||
var siguiente = _historialCompartido[indiceSiguiente];
|
||||
await AplicarRegistroHistorialAsync(siguiente);
|
||||
SeleccionarHistorialCompartido(siguiente.Id);
|
||||
}
|
||||
}
|
||||
|
||||
MostrarMensajeHistorial("Combinacion eliminada del historial compartido.");
|
||||
}
|
||||
|
||||
@@ -1204,7 +1204,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260727a)";
|
||||
private const string _versionApp = "(v-20260728a)";
|
||||
|
||||
|
||||
private double? _paradaElevG;
|
||||
|
||||
@@ -192,7 +192,10 @@ public partial class PlanificadorRutas
|
||||
? FactorPenalizacionDistanciaPiePorDefecto
|
||||
: factor;
|
||||
if (config.ParametroPenalizacionDistanciaPieMetros is { } parametro && double.IsFinite(parametro))
|
||||
_parametroPenalizacionDistanciaPieMetros = parametro;
|
||||
_parametroPenalizacionDistanciaPieMetros =
|
||||
Math.Abs(parametro - 100.0) < 0.0001
|
||||
? ParametroPenalizacionDistanciaPieMetrosPorDefecto
|
||||
: parametro;
|
||||
if (config.CoeficienteEsfuerzoAltura is { } coeficienteAltura && double.IsFinite(coeficienteAltura))
|
||||
_coeficienteEsfuerzoAltura =
|
||||
Math.Abs(coeficienteAltura - 1.6) < 0.0001 ||
|
||||
@@ -315,7 +318,7 @@ public partial class PlanificadorRutas
|
||||
_maxTransbordosPermitidos = 3;
|
||||
|
||||
_factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
|
||||
_parametroPenalizacionDistanciaPieMetros = 100.0;
|
||||
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||
_pesoTransbordoRankingMinutos = 4.0;
|
||||
_factorEquilibradoAndarBus = 12.0;
|
||||
@@ -365,7 +368,7 @@ public partial class PlanificadorRutas
|
||||
_maxTransbordosPermitidos = 3;
|
||||
|
||||
_factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
|
||||
_parametroPenalizacionDistanciaPieMetros = 100.0;
|
||||
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||
_pesoTransbordoRankingMinutos = 4.0;
|
||||
_factorEquilibradoAndarBus = 12.0;
|
||||
|
||||
@@ -463,7 +463,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
await CerrarPopupsAsync();
|
||||
|
||||
ThrowIfPintadoObsoleto(versionPintado);
|
||||
await FitRoutesJsAsync();
|
||||
await FitRoutesJsAsync(centroPreferido: _coordenadasOrigen);
|
||||
ThrowIfPintadoObsoleto(versionPintado);
|
||||
StateHasChanged();
|
||||
}
|
||||
@@ -505,7 +505,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
if (paradasTramo.Count == 0)
|
||||
continue;
|
||||
|
||||
var stops = BuildStopsDesdeParadas(paradasTramo);
|
||||
var stops = BuildStopsForTramoBus(tramo);
|
||||
|
||||
var id = $"route_stops_{indice}_{tramo.Variante.Linea.Codigo}_{tramo.Variante.CodigoItinerario}_{NormalizarSentidoCodigo(tramo.Variante.Sentido?.Codigo)}";
|
||||
var color = ColorCssDesdeVariante(tramo.Variante);
|
||||
|
||||
Reference in New Issue
Block a user