diff options
Diffstat (limited to 'src/Enmarcha.Backend')
5 files changed, 83 insertions, 24 deletions
diff --git a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs index 5c64efa..13fb430 100644 --- a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs +++ b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs @@ -117,7 +117,7 @@ public partial class ArrivalsController : ControllerBase }, Headsign = new HeadsignInfo { - Destination = item.Headsign + Destination = item.Trip.TripHeadsign ?? item.Headsign, }, Estimate = new ArrivalDetails { diff --git a/src/Enmarcha.Backend/Services/OtpService.cs b/src/Enmarcha.Backend/Services/OtpService.cs index 07c4d81..a01079f 100644 --- a/src/Enmarcha.Backend/Services/OtpService.cs +++ b/src/Enmarcha.Backend/Services/OtpService.cs @@ -303,6 +303,8 @@ public class OtpService var shortName = _feedService.NormalizeRouteShortName(feedId, leg.Route?.ShortName ?? string.Empty); var headsign = leg.Headsign; + var headsignTrimmed = headsign?.Trim(); + if (feedId == "vitrasa") { headsign = headsign?.Replace("*", ""); @@ -313,9 +315,21 @@ public class OtpService switch (shortName) { - case "A" when headsign != null && headsign.StartsWith("\"1\""): + case "A" when headsignTrimmed != null && + (headsignTrimmed.StartsWith("\"1\"", StringComparison.Ordinal) || + (headsignTrimmed.Length >= 1 && headsignTrimmed[0] == '1' && + (headsignTrimmed.Length == 1 || !char.IsDigit(headsignTrimmed[1])))): shortName = "A1"; - headsign = headsign.Replace("\"1\"", ""); + if (headsignTrimmed.StartsWith("\"1\"", StringComparison.Ordinal)) + { + headsign = headsignTrimmed[3..]; + } + else + { + headsign = headsignTrimmed[1..]; + } + + headsign = headsign.TrimStart(' ', '-', '.', ':'); break; case "6": headsign = headsign?.Replace("\"", ""); diff --git a/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs b/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs index 2d5f5d9..562b8f1 100644 --- a/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs +++ b/src/Enmarcha.Backend/Services/Processors/FeedConfigProcessor.cs @@ -47,6 +47,8 @@ public class FeedConfigProcessor : IArrivalsProcessor { arrival.Headsign.Destination = arrival.Headsign.Destination.Replace("*", ""); + var destinationTrimmed = arrival.Headsign.Destination.TrimStart(); + if (arrival.Headsign.Destination == "FORA DE SERVIZO.G.B.") { arrival.Headsign.Destination = "García Barbón, 7 (fora de servizo)"; @@ -55,9 +57,21 @@ public class FeedConfigProcessor : IArrivalsProcessor switch (arrival.Route.ShortName) { - case "A" when arrival.Headsign.Destination.StartsWith("\"1\""): + case "A" when destinationTrimmed.StartsWith("\"1\"", StringComparison.Ordinal) || + (destinationTrimmed.Length >= 1 && destinationTrimmed[0] == '1' && + (destinationTrimmed.Length == 1 || !char.IsDigit(destinationTrimmed[1]))): arrival.Route.ShortName = "A1"; - arrival.Headsign.Destination = arrival.Headsign.Destination.Replace("\"1\"", ""); + // NormalizeStopName() removes quotes for Vitrasa, so handle both "\"1\"" and leading "1". + if (destinationTrimmed.StartsWith("\"1\"", StringComparison.Ordinal)) + { + destinationTrimmed = destinationTrimmed.Substring(3); + } + else + { + destinationTrimmed = destinationTrimmed.Substring(1); + } + + arrival.Headsign.Destination = destinationTrimmed.TrimStart(' ', '-', '.', ':'); break; case "6": arrival.Headsign.Destination = arrival.Headsign.Destination.Replace("\"", ""); diff --git a/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs index 0ce54c2..d1d1e7d 100644 --- a/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs +++ b/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs @@ -66,7 +66,13 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor .Where(a => a.Route.ShortName.Trim() == estimate.Line.Trim()) .Select(a => { - var arrivalRouteNormalized = _feedService.NormalizeRouteNameForMatching(a.Headsign.Destination); + // Use tripHeadsign from GTFS if available, otherwise fall back to stop-level headsign + string scheduleHeadsign = a.Headsign.Destination; + if (a.RawOtpTrip is ArrivalsAtStopResponse.Arrival otpArr && !string.IsNullOrWhiteSpace(otpArr.Trip.TripHeadsign)) + { + scheduleHeadsign = otpArr.Trip.TripHeadsign; + } + var arrivalRouteNormalized = _feedService.NormalizeRouteNameForMatching(scheduleHeadsign); string? arrivalLongNameNormalized = null; string? arrivalLastStopNormalized = null; @@ -122,10 +128,38 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor var delayMinutes = estimate.Minutes - scheduledMinutes; arrival.Delay = new DelayBadge { Minutes = delayMinutes }; - // Prefer real-time headsign if available and different + string scheduledHeadsign = arrival.Headsign.Destination; + if (arrival.RawOtpTrip is ArrivalsAtStopResponse.Arrival otpArr && !string.IsNullOrWhiteSpace(otpArr.Trip.TripHeadsign)) + { + scheduledHeadsign = otpArr.Trip.TripHeadsign; + } + + _logger.LogDebug("Matched RT estimate: Line {Line}, RT: {RTRoute} ({RTMin}m), Scheduled: {ScheduledRoute} ({ScheduledMin}m), Delay: {Delay}m", + estimate.Line, estimate.Route, estimate.Minutes, scheduledHeadsign, scheduledMinutes, delayMinutes); + + // Prefer real-time headsign UNLESS it's just the last stop name (which is less informative) if (!string.IsNullOrWhiteSpace(estimate.Route)) { - arrival.Headsign.Destination = estimate.Route; + bool isJustLastStop = false; + + if (arrival.RawOtpTrip is ArrivalsAtStopResponse.Arrival otpArrival) + { + var lastStop = otpArrival.Trip.Stoptimes.LastOrDefault(); + if (lastStop != null) + { + var arrivalLastStopNormalized = _feedService.NormalizeRouteNameForMatching(lastStop.Stop.Name); + isJustLastStop = estimateRouteNormalized == arrivalLastStopNormalized; + } + } + + _logger.LogDebug("Headsign: RT='{RT}' vs Scheduled='{Scheduled}', IsJustLastStop={Last}, WillUseRT={Use}", + estimate.Route, scheduledHeadsign, isJustLastStop, !isJustLastStop); + + // Use real-time headsign unless it's just the final stop name + if (!isJustLastStop) + { + arrival.Headsign.Destination = estimate.Route; + } } // Calculate position @@ -150,25 +184,22 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor currentPosition = result.BusPosition; stopShapeIndex = result.StopIndex; - if (currentPosition != null) - { - _logger.LogInformation("Calculated position from OTP geometry for trip {TripId}: {Lat}, {Lon}", arrival.TripId, currentPosition.Latitude, currentPosition.Longitude); - } - // Populate Shape GeoJSON if (!context.IsReduced && currentPosition != null) { - var features = new List<object>(); - features.Add(new + var features = new List<object> { - type = "Feature", - geometry = new + new { - type = "LineString", - coordinates = decodedPoints.Select(p => new[] { p.Longitude, p.Latitude }).ToList() - }, - properties = new { type = "route" } - }); + type = "Feature", + geometry = new + { + type = "LineString", + coordinates = decodedPoints.Select(p => new[] { p.Longitude, p.Latitude }).ToList() + }, + properties = new { type = "route" } + } + }; // Add stops if available if (otpArrival.Trip.Stoptimes != null) diff --git a/src/Enmarcha.Backend/Types/Geoapify/GeoapifyModels.cs b/src/Enmarcha.Backend/Types/Geoapify/GeoapifyModels.cs index ac692ca..a15e54e 100644 --- a/src/Enmarcha.Backend/Types/Geoapify/GeoapifyModels.cs +++ b/src/Enmarcha.Backend/Types/Geoapify/GeoapifyModels.cs @@ -1,7 +1,7 @@ -using System.Text.Json.Serialization; - namespace Enmarcha.Backend.Types.Geoapify; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public class GeoapifyResult { public Result[] results { get; set; } |
