summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-03-16 13:01:36 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2026-03-16 13:01:36 +0100
commit3ce586243a49f34b36d0fe4099bbfb2631610f11 (patch)
treee1a654141bd8aef1852883aeef25392409c73891
parent99005bce74288a415ac748414e0f8b522e207c93 (diff)
New marquee generation logic for Xunta
-rw-r--r--src/Enmarcha.Backend/Controllers/ArrivalsController.cs6
-rw-r--r--src/Enmarcha.Backend/Controllers/RoutePlannerController.cs2
-rw-r--r--src/Enmarcha.Backend/Controllers/TileController.cs2
-rw-r--r--src/Enmarcha.Backend/Program.cs1
-rw-r--r--src/Enmarcha.Backend/Services/FeedService.cs27
-rw-r--r--src/Enmarcha.Backend/Services/OtpService.cs8
-rw-r--r--src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs2
-rw-r--r--src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs28
-rw-r--r--src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs99
-rw-r--r--src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs6
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; }
}