aboutsummaryrefslogtreecommitdiff
path: root/src/Costasdev.Busurbano.Backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/Costasdev.Busurbano.Backend')
-rw-r--r--src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs17
-rw-r--r--src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs53
-rw-r--r--src/Costasdev.Busurbano.Backend/Helpers/ContrastHelper.cs2
-rw-r--r--src/Costasdev.Busurbano.Backend/Program.cs2
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/FareService.cs49
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/FeedService.cs21
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/LineFormatterService.cs2
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/OtpService.cs187
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/Processors/CorunaRealTimeProcessor.cs2
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/Processors/VitrasaRealTimeProcessor.cs2
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/Providers/VitrasaTransitProvider.cs6
-rw-r--r--src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs2
12 files changed, 295 insertions, 50 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
index 957668a..6096b53 100644
--- a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
+++ b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs
@@ -75,11 +75,18 @@ public partial class ArrivalsController : ControllerBase
List<Arrival> arrivals = [];
foreach (var item in stop.Arrivals)
{
+ // Discard trip without pickup at stop
if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None))
{
continue;
}
+ // Discard on last stop
+ if (item.Trip.ArrivalStoptime.Stop.GtfsId == id)
+ {
+ continue;
+ }
+
if (item.Trip.Geometry?.Points != null)
{
_logger.LogDebug("Trip {TripId} has geometry", item.Trip.GtfsId);
@@ -133,6 +140,8 @@ public partial class ArrivalsController : ControllerBase
// Time after an arrival's time to still include it in the response. This is useful without real-time data, for delayed buses.
var timeThreshold = GetThresholdForFeed(id);
+ var (fallbackColor, fallbackTextColor) = _feedService.GetFallbackColourForFeed(feedId);
+
return Ok(new StopArrivalsResponse
{
StopCode = _feedService.NormalizeStopCode(feedId, stop.Code),
@@ -151,8 +160,10 @@ public partial class ArrivalsController : ControllerBase
{
GtfsId = r.GtfsId,
ShortName = _feedService.NormalizeRouteShortName(feedId, r.ShortName ?? ""),
- Colour = r.Color ?? "FFFFFF",
- TextColour = r.TextColor ?? "000000"
+ Colour = r.Color ?? fallbackColor,
+ TextColour = r.TextColor is null or "000000" ?
+ ContrastHelper.GetBestTextColour(r.Color ?? fallbackColor) :
+ r.TextColor
})],
Arrivals = [.. arrivals.Where(a => a.Estimate.Minutes >= timeThreshold)]
});
@@ -163,7 +174,7 @@ public partial class ArrivalsController : ControllerBase
{
string feedId = id.Split(':', 2)[0];
- if (feedId == "vitrasa" || feedId == "coruna")
+ if (feedId is "vitrasa" or "coruna")
{
return 0;
}
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs b/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs
index efddf82..823cfa5 100644
--- a/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs
+++ b/src/Costasdev.Busurbano.Backend/Controllers/RoutePlannerController.cs
@@ -1,18 +1,34 @@
+using System.Net;
+using Costasdev.Busurbano.Backend.Configuration;
using Costasdev.Busurbano.Backend.Services;
using Costasdev.Busurbano.Backend.Types.Planner;
+using Costasdev.Busurbano.Sources.OpenTripPlannerGql;
+using Costasdev.Busurbano.Sources.OpenTripPlannerGql.Queries;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
namespace Costasdev.Busurbano.Backend.Controllers;
[ApiController]
[Route("api/planner")]
-public class RoutePlannerController : ControllerBase
+public partial class RoutePlannerController : ControllerBase
{
+ private readonly ILogger<RoutePlannerController> _logger;
private readonly OtpService _otpService;
+ private readonly AppConfiguration _config;
+ private readonly HttpClient _httpClient;
- public RoutePlannerController(OtpService otpService)
+ public RoutePlannerController(
+ ILogger<RoutePlannerController> logger,
+ OtpService otpService,
+ IOptions<AppConfiguration> config,
+ HttpClient httpClient
+ )
{
+ _logger = logger;
_otpService = otpService;
+ _config = config.Value;
+ _httpClient = httpClient;
}
[HttpGet("autocomplete")]
@@ -44,18 +60,43 @@ public class RoutePlannerController : ControllerBase
[FromQuery] double fromLon,
[FromQuery] double toLat,
[FromQuery] double toLon,
- [FromQuery] DateTime? time = null,
+ [FromQuery] DateTimeOffset time,
[FromQuery] bool arriveBy = false)
{
try
{
- var plan = await _otpService.GetRoutePlanAsync(fromLat, fromLon, toLat, toLon, time, arriveBy);
+ var requestContent = PlanConnectionContent.Query(
+ new PlanConnectionContent.Args(fromLat, fromLon, toLat, toLon, time, arriveBy)
+ );
+
+ var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1");
+ request.Content = JsonContent.Create(new GraphClientRequest
+ {
+ Query = requestContent
+ });
+
+ var response = await _httpClient.SendAsync(request);
+ var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<PlanConnectionResponse>>();
+
+ Console.WriteLine(responseBody);
+
+ if (responseBody is not { IsSuccess: true } || responseBody.Data?.PlanConnection.Edges.Length == 0)
+ {
+ LogErrorFetchingRoutes(response.StatusCode, await response.Content.ReadAsStringAsync());
+ return StatusCode(500, "An error occurred while planning the route.");
+ }
+
+ var plan = _otpService.MapPlanResponse(responseBody.Data!);
return Ok(plan);
}
- catch (Exception)
+ catch (Exception e)
{
- // Log error
+ _logger.LogError("Exception planning route: {e}", e);
return StatusCode(500, "An error occurred while planning the route.");
}
}
+
+ [LoggerMessage(LogLevel.Error, "Error fetching route planning, received {statusCode} {responseBody}")]
+ partial void LogErrorFetchingRoutes(HttpStatusCode? statusCode, string responseBody);
+
}
diff --git a/src/Costasdev.Busurbano.Backend/Helpers/ContrastHelper.cs b/src/Costasdev.Busurbano.Backend/Helpers/ContrastHelper.cs
index e48660b..f9fd5f2 100644
--- a/src/Costasdev.Busurbano.Backend/Helpers/ContrastHelper.cs
+++ b/src/Costasdev.Busurbano.Backend/Helpers/ContrastHelper.cs
@@ -25,7 +25,7 @@ public static class ContrastHelper
double contrastWithWhite = (1.0 + 0.05) / (luminance + 0.05);
double contrastWithBlack = (luminance + 0.05) / 0.05;
- if (contrastWithWhite > 3)
+ if (contrastWithWhite >= 2.5)
{
return "#FFFFFF";
}
diff --git a/src/Costasdev.Busurbano.Backend/Program.cs b/src/Costasdev.Busurbano.Backend/Program.cs
index 7d52c29..7651cbc 100644
--- a/src/Costasdev.Busurbano.Backend/Program.cs
+++ b/src/Costasdev.Busurbano.Backend/Program.cs
@@ -21,6 +21,8 @@ builder.Services.AddMemoryCache();
builder.Services.AddSingleton<ShapeTraversalService>();
builder.Services.AddSingleton<FeedService>();
+builder.Services.AddSingleton<FareService>();
+builder.Services.AddSingleton<LineFormatterService>();
builder.Services.AddScoped<IArrivalsProcessor, VitrasaRealTimeProcessor>();
builder.Services.AddScoped<IArrivalsProcessor, CorunaRealTimeProcessor>();
diff --git a/src/Costasdev.Busurbano.Backend/Services/FareService.cs b/src/Costasdev.Busurbano.Backend/Services/FareService.cs
new file mode 100644
index 0000000..0e4fefc
--- /dev/null
+++ b/src/Costasdev.Busurbano.Backend/Services/FareService.cs
@@ -0,0 +1,49 @@
+using Costasdev.Busurbano.Backend.Configuration;
+using Costasdev.Busurbano.Backend.Types.Planner;
+using Microsoft.Extensions.Options;
+
+namespace Costasdev.Busurbano.Backend.Services;
+
+public record FareResult(double CashFareEuro, double CardFareEuro);
+
+public class FareService
+{
+ private readonly AppConfiguration _config;
+
+ public FareService(IOptions<AppConfiguration> config)
+ {
+ _config = config.Value;
+ }
+
+ public FareResult CalculateFare(IEnumerable<Leg> legs)
+ {
+ var busLegs = legs.Where(l => l.Mode != null && l.Mode.ToUpper() != "WALK").ToList();
+
+ // Cash fare logic
+ // TODO: In the future, this should depend on the operator/feed
+ var cashFare = busLegs.Count * 1.63; // Defaulting to Vitrasa for now
+
+ // Card fare logic (45-min transfer window)
+ int cardTicketsRequired = 0;
+ DateTime? lastTicketPurchased = null;
+ int tripsPaidWithTicket = 0;
+
+ foreach (var leg in busLegs)
+ {
+ if (lastTicketPurchased == null ||
+ (leg.StartTime - lastTicketPurchased.Value).TotalMinutes > 45 ||
+ tripsPaidWithTicket >= 3)
+ {
+ cardTicketsRequired++;
+ lastTicketPurchased = leg.StartTime;
+ tripsPaidWithTicket = 1;
+ }
+ else
+ {
+ tripsPaidWithTicket++;
+ }
+ }
+
+ return new FareResult(cashFare, cardTicketsRequired * 0.67);
+ }
+}
diff --git a/src/Costasdev.Busurbano.Backend/Services/FeedService.cs b/src/Costasdev.Busurbano.Backend/Services/FeedService.cs
index a8710b5..3ef079c 100644
--- a/src/Costasdev.Busurbano.Backend/Services/FeedService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/FeedService.cs
@@ -62,14 +62,22 @@ public class FeedService
public string NormalizeRouteShortName(string feedId, string shortName)
{
- if (feedId == "xunta" && shortName.StartsWith("XG") && shortName.Length >= 8)
+ if (feedId == "xunta" && shortName.StartsWith("XG"))
{
- // XG817014 -> 817.14
- var contract = shortName.Substring(2, 3);
- var lineStr = shortName.Substring(5);
- if (int.TryParse(lineStr, out int line))
+ if (shortName.Length >= 8)
+ {
+ // XG817014 -> 817.14
+ var contract = shortName.Substring(2, 3);
+ var lineStr = shortName.Substring(5);
+ if (int.TryParse(lineStr, out int line))
+ {
+ return $"{contract}.{line:D2}";
+ }
+ }
+ else if (shortName.Length > 2)
{
- return $"{contract}.{line:D2}";
+ // XG883 -> 883
+ return shortName.Substring(2);
}
}
return shortName;
@@ -91,6 +99,7 @@ public class FeedService
if (feedId == "vitrasa")
{
return name
+ .Trim()
.Replace("\"", "")
.Replace(" ", ", ")
.Trim();
diff --git a/src/Costasdev.Busurbano.Backend/Services/LineFormatterService.cs b/src/Costasdev.Busurbano.Backend/Services/LineFormatterService.cs
index 6ec40b1..db9f2a5 100644
--- a/src/Costasdev.Busurbano.Backend/Services/LineFormatterService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/LineFormatterService.cs
@@ -4,7 +4,7 @@ namespace Costasdev.Busurbano.Backend.Services;
public class LineFormatterService
{
- public static ConsolidatedCirculation Format(ConsolidatedCirculation circulation)
+ public ConsolidatedCirculation Format(ConsolidatedCirculation circulation)
{
circulation.Route = circulation.Route.Replace("*", "");
diff --git a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs
index eca8f50..7eba590 100644
--- a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs
@@ -1,8 +1,10 @@
using System.Globalization;
using System.Text;
using Costasdev.Busurbano.Backend.Configuration;
+using Costasdev.Busurbano.Backend.Helpers;
using Costasdev.Busurbano.Backend.Types.Otp;
using Costasdev.Busurbano.Backend.Types.Planner;
+using Costasdev.Busurbano.Sources.OpenTripPlannerGql.Queries;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
@@ -14,13 +16,19 @@ public class OtpService
private readonly AppConfiguration _config;
private readonly IMemoryCache _cache;
private readonly ILogger<OtpService> _logger;
+ private readonly FareService _fareService;
+ private readonly LineFormatterService _lineFormatter;
+ private readonly FeedService _feedService;
- public OtpService(HttpClient httpClient, IOptions<AppConfiguration> config, IMemoryCache cache, ILogger<OtpService> logger)
+ public OtpService(HttpClient httpClient, IOptions<AppConfiguration> config, IMemoryCache cache, ILogger<OtpService> logger, FareService fareService, LineFormatterService lineFormatter, FeedService feedService)
{
_httpClient = httpClient;
_config = config.Value;
_cache = cache;
_logger = logger;
+ _fareService = fareService;
+ _lineFormatter = lineFormatter;
+ _feedService = feedService;
}
public async Task<List<PlannerSearchResult>> GetAutocompleteAsync(string query)
@@ -244,39 +252,17 @@ public class OtpService
private PlannerPlace? MapPlace(OtpPlace? otpPlace)
{
if (otpPlace == null) return null;
+ var feedId = otpPlace.StopId?.Split(':')[0] ?? "unknown";
return new PlannerPlace
{
- Name = CorrectStopName(otpPlace.Name),
+ Name = _feedService.NormalizeStopName(feedId, otpPlace.Name),
Lat = otpPlace.Lat,
Lon = otpPlace.Lon,
StopId = otpPlace.StopId, // Use string directly
- StopCode = CorrectStopCode(otpPlace.StopCode)
+ StopCode = _feedService.NormalizeStopCode(feedId, otpPlace.StopCode ?? string.Empty)
};
}
- private string CorrectStopCode(string? stopId)
- {
- if (string.IsNullOrEmpty(stopId)) return stopId ?? string.Empty;
-
- var sb = new StringBuilder();
- foreach (var c in stopId)
- {
- if (char.IsNumber(c))
- {
- sb.Append(c);
- }
- }
-
- return int.Parse(sb.ToString()).ToString();
- }
-
- private string CorrectStopName(string? stopName)
- {
- if (string.IsNullOrEmpty(stopName)) return stopName ?? string.Empty;
-
- return stopName!.Replace(" ", ", ").Replace("\"", "");
- }
-
private Step MapStep(OtpWalkStep otpStep)
{
return new Step
@@ -351,4 +337,153 @@ public class OtpService
return poly;
}
+
+ public RoutePlan MapPlanResponse(PlanConnectionResponse response)
+ {
+ var itineraries = response.PlanConnection.Edges
+ .Select(e => MapItinerary(e.Node))
+ .ToList();
+
+ return new RoutePlan
+ {
+ Itineraries = itineraries
+ };
+ }
+
+ private Itinerary MapItinerary(PlanConnectionResponse.Node node)
+ {
+ var legs = node.Legs.Select(MapLeg).ToList();
+ var fares = _fareService.CalculateFare(legs);
+
+ return new Itinerary
+ {
+ DurationSeconds = node.DurationSeconds,
+ StartTime = DateTime.Parse(node.Start8601, null, DateTimeStyles.RoundtripKind),
+ EndTime = DateTime.Parse(node.End8601, null, DateTimeStyles.RoundtripKind),
+ WalkDistanceMeters = node.WalkDistance,
+ WalkTimeSeconds = node.WalkSeconds,
+ TransitTimeSeconds = node.DurationSeconds - node.WalkSeconds - node.WaitingSeconds,
+ WaitingTimeSeconds = node.WaitingSeconds,
+ Legs = legs,
+ CashFareEuro = fares.CashFareEuro,
+ CardFareEuro = fares.CardFareEuro
+ };
+ }
+
+ private Leg MapLeg(PlanConnectionResponse.Leg leg)
+ {
+ var feedId = leg.From.Stop?.GtfsId?.Split(':')[0] ?? "unknown";
+ var shortName = _feedService.NormalizeRouteShortName(feedId, leg.Route?.ShortName ?? string.Empty);
+ var headsign = leg.Headsign;
+
+ if (feedId == "vitrasa")
+ {
+ headsign = headsign?.Replace("*", "");
+ if (headsign == "FORA DE SERVIZO.G.B.")
+ {
+ headsign = "García Barbón, 7 (fora de servizo)";
+ }
+
+ switch (shortName)
+ {
+ case "A" when headsign != null && headsign.StartsWith("\"1\""):
+ shortName = "A1";
+ headsign = headsign.Replace("\"1\"", "");
+ break;
+ case "6":
+ headsign = headsign?.Replace("\"", "");
+ break;
+ case "FUT":
+ if (headsign == "CASTELAO-CAMELIAS-G.BARBÓN.M.GARRIDO")
+ {
+ shortName = "MAR";
+ headsign = "MARCADOR ⚽: CASTELAO-CAMELIAS-G.BARBÓN.M.GARRIDO";
+ }
+ else if (headsign == "P. ESPAÑA-T.VIGO-S.BADÍA")
+ {
+ shortName = "RIO";
+ headsign = "RÍO ⚽: P. ESPAÑA-T.VIGO-S.BADÍA";
+ }
+ else if (headsign == "NAVIA-BOUZAS-URZAIZ-G. ESPINO")
+ {
+ shortName = "GOL";
+ headsign = "GOL ⚽: NAVIA-BOUZAS-URZAIZ-G. ESPINO";
+ }
+ break;
+ }
+ }
+
+ var color = leg.Route?.Color;
+ var textColor = leg.Route?.TextColor;
+
+ if (string.IsNullOrEmpty(color) || color == "FFFFFF")
+ {
+ var (fallbackColor, fallbackTextColor) = _feedService.GetFallbackColourForFeed(feedId);
+ color = fallbackColor.Replace("#", "");
+ textColor = fallbackTextColor.Replace("#", "");
+ }
+ else if (string.IsNullOrEmpty(textColor) || textColor == "000000")
+ {
+ textColor = ContrastHelper.GetBestTextColour(color).Replace("#", "");
+ }
+
+ return new Leg
+ {
+ Mode = leg.Mode,
+ RouteName = leg.Route?.LongName,
+ RouteShortName = shortName,
+ RouteLongName = leg.Route?.LongName,
+ Headsign = headsign,
+ AgencyName = leg.Route?.Agency?.Name,
+ RouteColor = color,
+ RouteTextColor = textColor,
+ From = MapPlace(leg.From),
+ To = MapPlace(leg.To),
+ StartTime = DateTime.Parse(leg.Start.ScheduledTime8601, null, DateTimeStyles.RoundtripKind),
+ EndTime = DateTime.Parse(leg.End.ScheduledTime8601, null, DateTimeStyles.RoundtripKind),
+ DistanceMeters = leg.Distance,
+ Geometry = DecodePolyline(leg.LegGeometry?.Points),
+ Steps = leg.Steps.Select(MapStep).ToList(),
+ IntermediateStops = leg.StopCalls.Select(sc => MapPlace(sc.StopLocation)).ToList()
+ };
+ }
+
+ private PlannerPlace MapPlace(PlanConnectionResponse.LegPosition pos)
+ {
+ var feedId = pos.Stop?.GtfsId?.Split(':')[0] ?? "unknown";
+ return new PlannerPlace
+ {
+ Name = _feedService.NormalizeStopName(feedId, pos.Name),
+ Lat = pos.Latitude,
+ Lon = pos.Longitude,
+ StopId = pos.Stop?.GtfsId,
+ StopCode = _feedService.NormalizeStopCode(feedId, pos.Stop?.Code ?? string.Empty)
+ };
+ }
+
+ private PlannerPlace MapPlace(PlanConnectionResponse.StopLocation stop)
+ {
+ var feedId = stop.GtfsId?.Split(':')[0] ?? "unknown";
+ return new PlannerPlace
+ {
+ Name = _feedService.NormalizeStopName(feedId, stop.Name),
+ Lat = stop.Latitude,
+ Lon = stop.Longitude,
+ StopId = stop.GtfsId,
+ StopCode = _feedService.NormalizeStopCode(feedId, stop.Code ?? string.Empty)
+ };
+ }
+
+ private Step MapStep(PlanConnectionResponse.Step step)
+ {
+ return new Step
+ {
+ DistanceMeters = step.Distance,
+ RelativeDirection = step.RelativeDirection,
+ AbsoluteDirection = step.AbsoluteDirection,
+ StreetName = step.StreetName,
+ Lat = step.Latitude,
+ Lon = step.Longitude
+ };
+ }
}
diff --git a/src/Costasdev.Busurbano.Backend/Services/Processors/CorunaRealTimeProcessor.cs b/src/Costasdev.Busurbano.Backend/Services/Processors/CorunaRealTimeProcessor.cs
index ee98a1f..587917e 100644
--- a/src/Costasdev.Busurbano.Backend/Services/Processors/CorunaRealTimeProcessor.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/Processors/CorunaRealTimeProcessor.cs
@@ -73,8 +73,6 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
}
var arrival = bestMatch.Arrival;
- _logger.LogInformation("Matched Coruña real-time for line {Line}: {Scheduled}m -> {RealTime}m (diff: {Diff}m)",
- arrival.Route.ShortName, arrival.Estimate.Minutes, estimate.Minutes, bestMatch.TimeDiff);
var scheduledMinutes = arrival.Estimate.Minutes;
arrival.Estimate.Minutes = estimate.Minutes;
diff --git a/src/Costasdev.Busurbano.Backend/Services/Processors/VitrasaRealTimeProcessor.cs b/src/Costasdev.Busurbano.Backend/Services/Processors/VitrasaRealTimeProcessor.cs
index 5e0783d..f3a8d91 100644
--- a/src/Costasdev.Busurbano.Backend/Services/Processors/VitrasaRealTimeProcessor.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/Processors/VitrasaRealTimeProcessor.cs
@@ -111,8 +111,6 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor
if (bestMatch != null)
{
var arrival = bestMatch.Arrival;
- _logger.LogInformation("Matched Vitrasa real-time for line {Line}: {Scheduled}m -> {RealTime}m (diff: {Diff}m)",
- arrival.Route.ShortName, arrival.Estimate.Minutes, estimate.Minutes, bestMatch.TimeDiff);
var scheduledMinutes = arrival.Estimate.Minutes;
arrival.Estimate.Minutes = estimate.Minutes;
diff --git a/src/Costasdev.Busurbano.Backend/Services/Providers/VitrasaTransitProvider.cs b/src/Costasdev.Busurbano.Backend/Services/Providers/VitrasaTransitProvider.cs
index d245cd8..e54b66e 100644
--- a/src/Costasdev.Busurbano.Backend/Services/Providers/VitrasaTransitProvider.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/Providers/VitrasaTransitProvider.cs
@@ -15,13 +15,15 @@ public class VitrasaTransitProvider : ITransitProvider
private readonly VigoTransitApiClient _api;
private readonly AppConfiguration _configuration;
private readonly ShapeTraversalService _shapeService;
+ private readonly LineFormatterService _lineFormatter;
private readonly ILogger<VitrasaTransitProvider> _logger;
- public VitrasaTransitProvider(HttpClient http, IOptions<AppConfiguration> options, ShapeTraversalService shapeService, ILogger<VitrasaTransitProvider> logger)
+ public VitrasaTransitProvider(HttpClient http, IOptions<AppConfiguration> options, ShapeTraversalService shapeService, LineFormatterService lineFormatter, ILogger<VitrasaTransitProvider> logger)
{
_api = new VigoTransitApiClient(http);
_configuration = options.Value;
_shapeService = shapeService;
+ _lineFormatter = lineFormatter;
_logger = logger;
}
@@ -261,7 +263,7 @@ public class VitrasaTransitProvider : ITransitProvider
// Sort by ETA (RealTime minutes if present; otherwise Schedule minutes)
var sorted = consolidatedCirculations
.OrderBy(c => c.RealTime?.Minutes ?? c.Schedule!.Minutes)
- .Select(LineFormatterService.Format)
+ .Select(_lineFormatter.Format)
.ToList();
return sorted;
diff --git a/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs b/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs
index c31d12a..b2f4d6a 100644
--- a/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs
+++ b/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerResponse.cs
@@ -15,7 +15,7 @@ public class Itinerary
public double WalkTimeSeconds { get; set; }
public double TransitTimeSeconds { get; set; }
public double WaitingTimeSeconds { get; set; }
- public List<Leg> Legs { get; set; } = new();
+ public List<Leg> Legs { get; set; } = [];
public double? CashFareEuro { get; set; }
public double? CardFareEuro { get; set; }
}