From e7eb57bf492617f2b9be88d46c1cc708a2c17af4 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Fri, 12 Dec 2025 16:48:14 +0100 Subject: Improved version of the planner feature --- .../Configuration/AppConfiguration.cs | 4 ++ .../Services/OtpService.cs | 62 ++++++++++++++++++++-- .../Types/Otp/OtpModels.cs | 12 +++++ .../Types/Planner/PlannerModels.cs | 9 +++- 4 files changed, 80 insertions(+), 7 deletions(-) (limited to 'src/Costasdev.Busurbano.Backend') diff --git a/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs b/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs index a61fdb6..3224515 100644 --- a/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs +++ b/src/Costasdev.Busurbano.Backend/Configuration/AppConfiguration.cs @@ -13,4 +13,8 @@ public class AppConfiguration public int MaxWalkDistance { get; set; } = 1000; public int MaxWalkTime { get; set; } = 20; public int NumItineraries { get; set; } = 4; + + // Fare Configuration + public double FareCashPerBus { get; set; } = 1.63; + public double FareCardPerBus { get; set; } = 0.67; } diff --git a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs index 4c22ff5..77eddd3 100644 --- a/src/Costasdev.Busurbano.Backend/Services/OtpService.cs +++ b/src/Costasdev.Busurbano.Backend/Services/OtpService.cs @@ -141,14 +141,37 @@ public class OtpService private RoutePlan MapToRoutePlan(OtpPlan otpPlan) { + // Compute time offset: OTP's "date" field is the server time in millis + var otpServerTime = DateTimeOffset.FromUnixTimeMilliseconds(otpPlan.Date); + var now = DateTimeOffset.Now; + var timeOffsetSeconds = (long)(otpServerTime - now).TotalSeconds; + return new RoutePlan { - Itineraries = otpPlan.Itineraries.Select(MapItinerary).ToList() + Itineraries = otpPlan.Itineraries.Select(MapItinerary).ToList(), + TimeOffsetSeconds = timeOffsetSeconds }; } private Itinerary MapItinerary(OtpItinerary otpItinerary) { + var legs = otpItinerary.Legs.Select(MapLeg).ToList(); + var busLegs = legs.Where(leg => leg.Mode != null && leg.Mode.ToUpper() != "WALK"); + + var cashFareEuro = busLegs.Count() * _config.FareCashPerBus; + + int cardTicketsRequired = 0; + DateTime? lastTicketPurchased = null; + + foreach (var leg in busLegs) + { + if (lastTicketPurchased == null || (leg.StartTime - lastTicketPurchased.Value).TotalMinutes > 45) + { + cardTicketsRequired++; + lastTicketPurchased = leg.StartTime; + } + } + return new Itinerary { DurationSeconds = otpItinerary.Duration, @@ -158,7 +181,9 @@ public class OtpService WalkTimeSeconds = otpItinerary.WalkTime, TransitTimeSeconds = otpItinerary.TransitTime, WaitingTimeSeconds = otpItinerary.WaitingTime, - Legs = otpItinerary.Legs.Select(MapLeg).ToList() + Legs = legs, + CashFareEuro = cashFareEuro, + CardFareEuro = cardTicketsRequired * _config.FareCardPerBus }; } @@ -172,12 +197,16 @@ public class OtpService RouteLongName = otpLeg.RouteLongName, Headsign = otpLeg.Headsign, AgencyName = otpLeg.AgencyName, + RouteColor = otpLeg.RouteColor, + RouteTextColor = otpLeg.RouteTextColor, From = MapPlace(otpLeg.From), To = MapPlace(otpLeg.To), StartTime = DateTimeOffset.FromUnixTimeMilliseconds(otpLeg.StartTime).LocalDateTime, EndTime = DateTimeOffset.FromUnixTimeMilliseconds(otpLeg.EndTime).LocalDateTime, + DistanceMeters = otpLeg.Distance, Geometry = DecodePolyline(otpLeg.LegGeometry?.Points), - Steps = otpLeg.Steps.Select(MapStep).ToList() + Steps = otpLeg.Steps.Select(MapStep).ToList(), + IntermediateStops = otpLeg.IntermediateStops.Select(MapPlace).Where(p => p != null).Cast().ToList() }; } @@ -186,14 +215,37 @@ public class OtpService if (otpPlace == null) return null; return new PlannerPlace { - Name = otpPlace.Name, + Name = CorrectStopName(otpPlace.Name), Lat = otpPlace.Lat, Lon = otpPlace.Lon, StopId = otpPlace.StopId, // Use string directly - StopCode = otpPlace.StopCode + StopCode = CorrectStopCode(otpPlace.StopCode) }; } + 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 diff --git a/src/Costasdev.Busurbano.Backend/Types/Otp/OtpModels.cs b/src/Costasdev.Busurbano.Backend/Types/Otp/OtpModels.cs index 3d3de17..93c4d8b 100644 --- a/src/Costasdev.Busurbano.Backend/Types/Otp/OtpModels.cs +++ b/src/Costasdev.Busurbano.Backend/Types/Otp/OtpModels.cs @@ -102,6 +102,18 @@ public class OtpLeg [JsonPropertyName("headsign")] public string? Headsign { get; set; } + + [JsonPropertyName("distance")] + public double Distance { get; set; } + + [JsonPropertyName("routeColor")] + public string? RouteColor { get; set; } + + [JsonPropertyName("routeTextColor")] + public string? RouteTextColor { get; set; } + + [JsonPropertyName("intermediateStops")] + public List IntermediateStops { get; set; } = new(); } public class OtpPlace diff --git a/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerModels.cs b/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerModels.cs index 30e5e2d..c31d12a 100644 --- a/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerModels.cs +++ b/src/Costasdev.Busurbano.Backend/Types/Planner/PlannerModels.cs @@ -1,10 +1,9 @@ -using System.Text.Json.Serialization; - namespace Costasdev.Busurbano.Backend.Types.Planner; public class RoutePlan { public List Itineraries { get; set; } = new(); + public long? TimeOffsetSeconds { get; set; } } public class Itinerary @@ -17,6 +16,8 @@ public class Itinerary public double TransitTimeSeconds { get; set; } public double WaitingTimeSeconds { get; set; } public List Legs { get; set; } = new(); + public double? CashFareEuro { get; set; } + public double? CardFareEuro { get; set; } } public class Leg @@ -27,6 +28,8 @@ public class Leg public string? RouteLongName { get; set; } public string? Headsign { get; set; } public string? AgencyName { get; set; } + public string? RouteColor { get; set; } + public string? RouteTextColor { get; set; } public PlannerPlace? From { get; set; } public PlannerPlace? To { get; set; } public DateTime StartTime { get; set; } @@ -37,6 +40,8 @@ public class Leg public PlannerGeometry? Geometry { get; set; } public List Steps { get; set; } = new(); + + public List IntermediateStops { get; set; } = new(); } public class PlannerPlace -- cgit v1.3