diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2026-03-16 13:01:36 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2026-03-16 13:01:36 +0100 |
| commit | 3ce586243a49f34b36d0fe4099bbfb2631610f11 (patch) | |
| tree | e1a654141bd8aef1852883aeef25392409c73891 | |
| parent | 99005bce74288a415ac748414e0f8b522e207c93 (diff) | |
New marquee generation logic for Xunta
10 files changed, 111 insertions, 70 deletions
diff --git a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs index 03286c4..861038f 100644 --- a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs +++ b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs @@ -65,7 +65,7 @@ public partial class ArrivalsController : ControllerBase return Ok(new StopArrivalsResponse { StopCode = _feedService.NormalizeStopCode(feedId, stop.Code), - StopName = _feedService.NormalizeStopName(feedId, stop.Name), + StopName = FeedService.NormalizeStopName(feedId, stop.Name), StopLocation = new Position { Latitude = stop.Lat, Longitude = stop.Lon }, Routes = [.. _feedService.ConsolidateRoutes(feedId, stop.Routes @@ -166,7 +166,7 @@ public partial class ArrivalsController : ControllerBase List<Arrival> arrivals = []; foreach (var item in stop.Arrivals) { - if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None)) continue; + //if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None)) continue; if ( item.Trip.ArrivalStoptime.Stop.GtfsId == id && item.Trip.DepartureStoptime.Stop.GtfsId != id @@ -305,7 +305,7 @@ public partial class ArrivalsController : ControllerBase var feedId = s.GtfsId.Split(':', 2)[0]; var (fallbackColor, _) = _feedService.GetFallbackColourForFeed(feedId); var code = _feedService.NormalizeStopCode(feedId, s.Code ?? ""); - var name = _feedService.NormalizeStopName(feedId, s.Name); + var name = FeedService.NormalizeStopName(feedId, s.Name); return (dynamic)new { diff --git a/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs b/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs index 3ffb02f..489f264 100644 --- a/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs +++ b/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs @@ -163,7 +163,7 @@ public partial class RoutePlannerController : ControllerBase var stops = allStopsRaw.Select(s => { var feedId = s.GtfsId.Split(':')[0]; - var name = _feedService.NormalizeStopName(feedId, s.Name); + var name = FeedService.NormalizeStopName(feedId, s.Name); var code = _feedService.NormalizeStopCode(feedId, s.Code ?? string.Empty); return new PlannerSearchResult diff --git a/src/Enmarcha.Backend/Controllers/TileController.cs b/src/Enmarcha.Backend/Controllers/TileController.cs index 300f9db..bf89a08 100644 --- a/src/Enmarcha.Backend/Controllers/TileController.cs +++ b/src/Enmarcha.Backend/Controllers/TileController.cs @@ -125,7 +125,7 @@ public class TileController : ControllerBase { "feed", idParts[0] }, // The public identifier, usually feed:code or feed:id, recognisable by users and in other systems { "code", $"{idParts[0]}:{codeWithinFeed}" }, - { "name", _feedService.NormalizeStopName(feedId, stop.Name) }, + { "name", FeedService.NormalizeStopName(feedId, stop.Name) }, { "icon", GetIconNameForFeed(feedId) }, { "transitKind", GetTransitKind(feedId) } } diff --git a/src/Enmarcha.Backend/Program.cs b/src/Enmarcha.Backend/Program.cs index 46383b0..6eddfc8 100644 --- a/src/Enmarcha.Backend/Program.cs +++ b/src/Enmarcha.Backend/Program.cs @@ -133,7 +133,6 @@ builder.Services.AddScoped<IArrivalsProcessor, RenfeRealTimeProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, FilterAndSortProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, NextStopsProcessor>(); -builder.Services.AddScoped<IArrivalsProcessor, MarqueeProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, ShapeProcessor>(); builder.Services.AddScoped<IArrivalsProcessor, FeedConfigProcessor>(); builder.Services.AddScoped<ArrivalsPipeline>(); diff --git a/src/Enmarcha.Backend/Services/FeedService.cs b/src/Enmarcha.Backend/Services/FeedService.cs index c3e33fd..4ea3752 100644 --- a/src/Enmarcha.Backend/Services/FeedService.cs +++ b/src/Enmarcha.Backend/Services/FeedService.cs @@ -148,7 +148,7 @@ public class FeedService return result; } - public string NormalizeStopName(string feedId, string name) + public static string NormalizeStopName(string feedId, string name) { if (feedId == "vitrasa") { @@ -171,7 +171,7 @@ public class FeedService return Regex.Replace(normalized, @"[^a-z0-9]", ""); } - public string GetStreetName(string originalName) + public static string GetStreetName(string originalName) { var name = RemoveQuotationMarks.Replace(originalName, "").Trim(); var match = StreetNameRegex.Match(name); @@ -189,29 +189,6 @@ public class FeedService return streetName.Trim(); } - public string? GenerateMarquee(string feedId, List<string> nextStops) - { - if (nextStops.Count == 0) return null; - - if (feedId is "vitrasa" or "tranvias" or "tussa" or "ourense") - { - var streets = nextStops - .Select(GetStreetName) - .Where(s => !string.IsNullOrWhiteSpace(s)) - .Distinct() - .ToList(); - - return string.Join(" - ", streets); - } - - return feedId switch - { - "xunta" => string.Join(" > ", nextStops), - "renfe" => string.Join(" - ", nextStops), - _ => string.Join(", ", nextStops.Take(4)) - }; - } - public bool IsStopHidden(string stopId) { return HiddenStops.Contains(stopId); diff --git a/src/Enmarcha.Backend/Services/OtpService.cs b/src/Enmarcha.Backend/Services/OtpService.cs index 0de06bf..8724cda 100644 --- a/src/Enmarcha.Backend/Services/OtpService.cs +++ b/src/Enmarcha.Backend/Services/OtpService.cs @@ -101,7 +101,7 @@ public class OtpService { Id = s.GtfsId, Code = _feedService.NormalizeStopCode(feedId, s.Code ?? string.Empty), - Name = _feedService.NormalizeStopName(feedId, s.Name), + Name = FeedService.NormalizeStopName(feedId, s.Name), Lat = s.Lat, Lon = s.Lon, ScheduledDepartures = pattern.TripsForDate @@ -180,7 +180,7 @@ public class OtpService var feedId = otpPlace.StopId?.Split(':')[0] ?? "unknown"; return new PlannerPlace { - Name = _feedService.NormalizeStopName(feedId, otpPlace.Name!), + Name = FeedService.NormalizeStopName(feedId, otpPlace.Name!), Lat = otpPlace.Lat, Lon = otpPlace.Lon, StopId = otpPlace.StopId, // Use string directly @@ -379,7 +379,7 @@ public class OtpService var feedId = pos.Stop?.GtfsId?.Split(':')[0] ?? "unknown"; return new PlannerPlace { - Name = _feedService.NormalizeStopName(feedId, pos.Name), + Name = FeedService.NormalizeStopName(feedId, pos.Name), Lat = pos.Latitude, Lon = pos.Longitude, StopId = pos.Stop?.GtfsId, @@ -393,7 +393,7 @@ public class OtpService var feedId = stop.GtfsId?.Split(':')[0] ?? "unknown"; return new PlannerPlace { - Name = _feedService.NormalizeStopName(feedId, stop.Name), + Name = FeedService.NormalizeStopName(feedId, stop.Name), Lat = stop.Latitude, Lon = stop.Longitude, StopId = stop.GtfsId, diff --git a/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs b/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs index 196091a..9fc46c7 100644 --- a/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs +++ b/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs @@ -20,7 +20,7 @@ public class FeedConfigProcessor : IArrivalsProcessor foreach (var arrival in context.Arrivals) { arrival.Route.ShortName = _feedService.NormalizeRouteShortName(feedId, arrival.Route.ShortName); - arrival.Headsign.Destination = _feedService.NormalizeStopName(feedId, arrival.Headsign.Destination); + arrival.Headsign.Destination = FeedService.NormalizeStopName(feedId, arrival.Headsign.Destination); // Apply Vitrasa-specific line formatting if (feedId == "vitrasa") diff --git a/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs deleted file mode 100644 index d5ff58e..0000000 --- a/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Enmarcha.Backend.Services.Processors; - -public class MarqueeProcessor : IArrivalsProcessor -{ - private readonly FeedService _feedService; - - public MarqueeProcessor(FeedService feedService) - { - _feedService = feedService; - } - - public Task ProcessAsync(ArrivalsContext context) - { - if (context.IsNano) return Task.CompletedTask; - - var feedId = context.StopId.Split(':')[0]; - - foreach (var arrival in context.Arrivals) - { - if (string.IsNullOrEmpty(arrival.Headsign.Marquee)) - { - arrival.Headsign.Marquee = _feedService.GenerateMarquee(feedId, arrival.NextStops); - } - } - - return Task.CompletedTask; - } -} diff --git a/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs b/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs index 5bdb5bb..84e0d0f 100644 --- a/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs +++ b/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs @@ -1,3 +1,4 @@ +using System.Text; using Enmarcha.Sources.OpenTripPlannerGql.Queries; namespace Enmarcha.Backend.Services.Processors; @@ -20,17 +21,105 @@ public class NextStopsProcessor : IArrivalsProcessor foreach (var arrival in context.Arrivals) { if (arrival.RawOtpTrip is not ArrivalsAtStopResponse.Arrival otpArrival) continue; + if (arrival.Headsign.Marquee is not null) continue; // Filter stoptimes that are after the current stop's departure var currentStopDeparture = otpArrival.ScheduledDepartureSeconds; - arrival.NextStops = otpArrival.Trip.Stoptimes - .Where(s => s.ScheduledDeparture > currentStopDeparture) - .OrderBy(s => s.ScheduledDeparture) - .Select(s => _feedService.NormalizeStopName(feedId, s.Stop.Name)) - .ToList(); + if (feedId == "xunta") + { + arrival.NextStops = otpArrival.Trip.Stoptimes + .Where(s => s.ScheduledDeparture > currentStopDeparture) + .OrderBy(s => s.ScheduledDeparture) + .Select(s => s.Stop.Description) + .Distinct() + .ToList(); + } + else + { + arrival.NextStops = otpArrival.Trip.Stoptimes + .Where(s => s.ScheduledDeparture > currentStopDeparture) + .OrderBy(s => s.ScheduledDeparture) + .Select(s => FeedService.NormalizeStopName(feedId, s.Stop.Name)) + .ToList(); + } + + arrival.Headsign.Marquee = GenerateMarquee(feedId, arrival.NextStops); } return Task.CompletedTask; } + + private static string? GenerateMarquee(string feedId, List<string> nextStops) + { + if (nextStops.Count == 0) return null; + + if (feedId is "vitrasa" or "tranvias" or "tussa" or "ourense") + { + var streets = nextStops + .Select(FeedService.GetStreetName) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Distinct() + .ToList(); + + return string.Join(" - ", streets); + } + + if (feedId == "xunta") + { + var points = nextStops + .Select(SplitXuntaStopDescription) + .ToList(); + + List<string> seenConcellos = new(); + List<string> seenParroquias = new(); + List<string> items = []; + + foreach (var (parroquia, concello) in points) + { + // Santiago de Compostela -- Santiago de Compostela > Conxo -- Santiago de Compostela > Biduído -- Ames > Calo -- Teo > Bugallido -- Ames + // Santiago de Compostela -> Conxo -> Bidueiro (Ames) -> Calo (Teo) -> Bugallido + string item = ""; + + if (!seenParroquias.Contains(parroquia)) + { + seenParroquias.Add(parroquia); + item += $"{parroquia}"; + + if (parroquia == concello) + { + seenConcellos.Add(concello); + } + + if (!seenConcellos.Contains(concello)) + { + seenConcellos.Add(concello); + item += $" ({concello})"; + } + + items.Add(item); + } + + } + + return string.Join(" > ", items); + } + + return feedId switch + { + "renfe" => string.Join(" - ", nextStops), + _ => string.Join(", ", nextStops.Take(4)) + }; + } + + private static (string parroquia, string concello) SplitXuntaStopDescription(string stopName) + { + var parts = stopName.Split(" -- ", 2); + if (parts.Length != 2) + { + return ("", ""); // TODO: Throw + } + + return (parts[0], parts[1]); + } } diff --git a/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs b/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs index 49dbd43..7cf107a 100644 --- a/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs +++ b/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Text.Json.Serialization; -using Enmarcha.Sources.OpenTripPlannerGql; namespace Enmarcha.Sources.OpenTripPlannerGql.Queries; @@ -19,6 +18,7 @@ public class ArrivalsAtStopContent : IGraphRequest<ArrivalsAtStopContent.Args> stop(id:""{args.Id}"") {{ code name + desc lat lon routes {{ @@ -63,6 +63,7 @@ public class ArrivalsAtStopContent : IGraphRequest<ArrivalsAtStopContent.Args> stop {{ gtfsId name + desc lat lon }} @@ -86,6 +87,8 @@ public class ArrivalsAtStopResponse : AbstractGraphResponse [JsonPropertyName("name")] public required string Name { get; set; } + [JsonPropertyName("desc")] public required string Description { get; set; } + [JsonPropertyName("lat")] public double Lat { get; set; } [JsonPropertyName("lon")] public double Lon { get; set; } @@ -157,6 +160,7 @@ public class ArrivalsAtStopResponse : AbstractGraphResponse { [JsonPropertyName("gtfsId")] public string? GtfsId { get; set; } [JsonPropertyName("name")] public required string Name { get; set; } + [JsonPropertyName("desc")] public required string Description { get; set; } [JsonPropertyName("lat")] public double Lat { get; set; } [JsonPropertyName("lon")] public double Lon { get; set; } } |
