summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-03-13 16:49:10 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2026-03-13 16:49:30 +0100
commitee69c62adc5943a1dbd154df5142c0e726bdd317 (patch)
tree5874249173aa249d4d497733ef9fc410e64ab664
parent90ad5395f6310da86fee9a29503e58ea74f3078b (diff)
feat(routes): add realtime estimates panel with pattern-aware styling
- New GET /api/stops/estimates endpoint (nano mode: tripId, patternId, estimate, delay only) - useStopEstimates hook wiring estimates to routes-$id stop panel - Pattern-aware styling: dim schedules and estimates from other patterns - Past scheduled departures shown with strikethrough instead of hidden - Persist selected pattern in URL hash (replace navigation, no history push) - Fix planner arrivals using new estimates endpoint
-rw-r--r--src/Enmarcha.Backend/Controllers/ArrivalsController.cs165
-rw-r--r--src/Enmarcha.Backend/Controllers/TileController.cs2
-rw-r--r--src/Enmarcha.Backend/Extensions/StopScheduleExtensions.cs58
-rw-r--r--src/Enmarcha.Backend/Services/ArrivalsPipeline.cs6
-rw-r--r--src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs3
-rw-r--r--src/Enmarcha.Backend/Services/Processors/CtagShuttleRealTimeProcessor.cs1
-rw-r--r--src/Enmarcha.Backend/Services/Processors/FilterAndSortProcessor.cs4
-rw-r--r--src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs2
-rw-r--r--src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs2
-rw-r--r--src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs2
-rw-r--r--src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs2
-rw-r--r--src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs3
-rw-r--r--src/Enmarcha.Backend/Types/Arrivals/Arrival.cs3
-rw-r--r--src/Enmarcha.Backend/Types/Arrivals/StopEstimatesResponse.cs19
-rw-r--r--src/Enmarcha.Backend/Types/ConsolidatedCirculation.cs12
-rw-r--r--src/Enmarcha.Backend/Types/StopSchedule.cs1542
-rw-r--r--src/Enmarcha.Backend/Types/VigoSchedules.cs78
-rw-r--r--src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs12
-rw-r--r--src/frontend/app/api/arrivals.ts30
-rw-r--r--src/frontend/app/api/schema.ts14
-rw-r--r--src/frontend/app/components/stop/StopMapModal.tsx5
-rw-r--r--src/frontend/app/data/PlannerApi.ts2
-rw-r--r--src/frontend/app/hooks/useArrivals.ts21
-rw-r--r--src/frontend/app/routes/planner.tsx122
-rw-r--r--src/frontend/app/routes/routes-$id.tsx187
-rw-r--r--src/frontend/app/routes/stops-$id.tsx1
26 files changed, 377 insertions, 1921 deletions
diff --git a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs
index 50d4012..7ca3ab9 100644
--- a/src/Enmarcha.Backend/Controllers/ArrivalsController.cs
+++ b/src/Enmarcha.Backend/Controllers/ArrivalsController.cs
@@ -54,58 +54,126 @@ public partial class ArrivalsController : ControllerBase
activity?.SetTag("stop.id", id);
activity?.SetTag("reduced", reduced);
+ var result = await FetchAndProcessArrivalsAsync(id, reduced, nano: false);
+ if (result is null) return StatusCode(500, "Error fetching stop data");
+ var (stop, context) = result.Value;
+
+ var feedId = id.Split(':')[0];
+ var timeThreshold = GetThresholdForFeed(id);
+ var (fallbackColor, fallbackTextColor) = _feedService.GetFallbackColourForFeed(feedId);
+
+ return Ok(new StopArrivalsResponse
+ {
+ StopCode = _feedService.NormalizeStopCode(feedId, stop.Code),
+ StopName = _feedService.NormalizeStopName(feedId, stop.Name),
+ StopLocation = new Position { Latitude = stop.Lat, Longitude = stop.Lon },
+ Routes = [.. _feedService.ConsolidateRoutes(feedId,
+ stop.Routes
+ .OrderBy(r => SortingHelper.GetRouteSortKey(r.ShortName, r.GtfsId))
+ .Select(r => new RouteInfo
+ {
+ GtfsId = r.GtfsId,
+ ShortName = _feedService.NormalizeRouteShortName(feedId, r.ShortName ?? ""),
+ Colour = r.Color ?? fallbackColor,
+ TextColour = r.TextColor is null or "000000" ?
+ ContrastHelper.GetBestTextColour(r.Color ?? fallbackColor) :
+ r.TextColor
+ }))],
+ Arrivals = [.. context.Arrivals.Where(a => a.Estimate.Minutes >= timeThreshold)],
+ Usage = context.Usage
+ });
+ }
+
+ [HttpGet("estimates")]
+ public async Task<IActionResult> GetEstimates(
+ [FromQuery] string stop,
+ [FromQuery] string route,
+ [FromQuery] string? via
+ )
+ {
+ if (string.IsNullOrWhiteSpace(stop) || string.IsNullOrWhiteSpace(route))
+ return BadRequest("'stop' and 'route' are required.");
+
+ using var activity = Telemetry.Source.StartActivity("GetEstimates");
+ activity?.SetTag("stop.id", stop);
+ activity?.SetTag("route.id", route);
+ activity?.SetTag("via.id", via);
+
+ var result = await FetchAndProcessArrivalsAsync(stop, reduced: false, nano: true);
+ if (result is null) return StatusCode(500, "Error fetching stop data");
+ var (_, context) = result.Value;
+
+ // Annotate each arrival with its OTP pattern ID
+ foreach (var arrival in context.Arrivals)
+ {
+ if (arrival.RawOtpTrip is ArrivalsAtStopResponse.Arrival otpArrival)
+ arrival.PatternId = otpArrival.Trip.Pattern?.Id;
+ }
+
+ // Filter by route GTFS ID
+ var timeThreshold = GetThresholdForFeed(stop);
+ var filtered = context.Arrivals
+ .Where(a => a.Route.GtfsId == route && a.Estimate.Minutes >= timeThreshold)
+ .ToList();
+
+ // Optionally filter by via stop: keep only trips whose remaining stoptimes include the via stop
+ if (!string.IsNullOrWhiteSpace(via))
+ {
+ filtered = filtered.Where(a =>
+ {
+ if (a.RawOtpTrip is not ArrivalsAtStopResponse.Arrival otpArrival) return false;
+ var stoptimes = otpArrival.Trip.Stoptimes;
+ var originIdx = stoptimes.FindIndex(s => s.Stop.GtfsId == stop);
+ var searchFrom = originIdx >= 0 ? originIdx + 1 : 0;
+ return stoptimes.Skip(searchFrom).Any(s => s.Stop.GtfsId == via);
+ }).ToList();
+ }
+
+ var estimates = filtered.Select(a => new ArrivalEstimate
+ {
+ TripId = a.TripId,
+ PatternId = a.PatternId,
+ Estimate = a.Estimate,
+ Delay = a.Delay
+ }).ToList();
+
+ return Ok(new StopEstimatesResponse { Arrivals = estimates });
+ }
+
+ private async Task<(ArrivalsAtStopResponse.StopItem Stop, ArrivalsContext Context)?> FetchAndProcessArrivalsAsync(
+ string id, bool reduced, bool nano)
+ {
var tz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid");
var nowLocal = TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz);
- var todayLocal = nowLocal.Date;
- var requestContent = ArrivalsAtStopContent.Query(
- new ArrivalsAtStopContent.Args(id, reduced)
- );
+ var requestContent = ArrivalsAtStopContent.Query(new ArrivalsAtStopContent.Args(id, reduced || nano));
var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1");
- request.Content = JsonContent.Create(new GraphClientRequest
- {
- Query = requestContent
- });
+ request.Content = JsonContent.Create(new GraphClientRequest { Query = requestContent });
var response = await _httpClient.SendAsync(request);
var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<ArrivalsAtStopResponse>>();
if (responseBody is not { IsSuccess: true } || responseBody.Data?.Stop == null)
{
- activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, "Error fetching stop data from OTP");
LogErrorFetchingStopData(response.StatusCode, await response.Content.ReadAsStringAsync());
- return StatusCode(500, "Error fetching stop data");
+ return null;
}
var stop = responseBody.Data.Stop;
_logger.LogInformation("Fetched {Count} arrivals for stop {StopName} ({StopId})", stop.Arrivals.Count, stop.Name, id);
- activity?.SetTag("arrivals.count", stop.Arrivals.Count);
List<Arrival> arrivals = [];
foreach (var item in stop.Arrivals)
{
- // Discard trip without pickup at stop
- if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None))
- {
- continue;
- }
+ if (item.PickupTypeParsed.Equals(ArrivalsAtStopResponse.PickupType.None)) continue;
+ if (item.Trip.ArrivalStoptime.Stop.GtfsId == id) continue;
- // Discard on last stop
- if (item.Trip.ArrivalStoptime.Stop.GtfsId == id)
- {
- continue;
- }
-
- // Calculate departure time using the service day in the feed's timezone (Europe/Madrid)
- // This ensures we treat ScheduledDepartureSeconds as relative to the local midnight of the service day
var serviceDayLocal = TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeSeconds(item.ServiceDay), tz);
var departureTime = serviceDayLocal.Date.AddSeconds(item.ScheduledDepartureSeconds);
-
var minutesToArrive = (int)(departureTime - nowLocal).TotalMinutes;
- //var isRunning = departureTime < nowLocal;
- Arrival arrival = new()
+ arrivals.Add(new Arrival
{
TripId = item.Trip.GtfsId,
Route = new RouteInfo
@@ -115,19 +183,14 @@ public partial class ArrivalsController : ControllerBase
Colour = item.Trip.Route.Color ?? "FFFFFF",
TextColour = item.Trip.Route.TextColor ?? "000000"
},
- Headsign = new HeadsignInfo
- {
- Destination = item.Trip.TripHeadsign ?? item.Headsign,
- },
+ Headsign = new HeadsignInfo { Destination = item.Trip.TripHeadsign ?? item.Headsign },
Estimate = new ArrivalDetails
{
Minutes = minutesToArrive,
Precision = departureTime < nowLocal.AddMinutes(-1) ? ArrivalPrecision.Past : ArrivalPrecision.Scheduled
},
RawOtpTrip = item
- };
-
- arrivals.Add(arrival);
+ });
}
var context = new ArrivalsContext
@@ -135,6 +198,7 @@ public partial class ArrivalsController : ControllerBase
StopId = id,
StopCode = stop.Code,
IsReduced = reduced,
+ IsNano = nano,
Arrivals = arrivals,
NowLocal = nowLocal,
StopLocation = new Position { Latitude = stop.Lat, Longitude = stop.Lon }
@@ -142,38 +206,7 @@ public partial class ArrivalsController : ControllerBase
await _pipeline.ExecuteAsync(context);
- var feedId = id.Split(':')[0];
-
- // 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),
- StopName = _feedService.NormalizeStopName(feedId, stop.Name),
- StopLocation = new Position
- {
- Latitude = stop.Lat,
- Longitude = stop.Lon
- },
- Routes = [.. _feedService.ConsolidateRoutes(feedId,
- stop.Routes
- .OrderBy(r => SortingHelper.GetRouteSortKey(r.ShortName, r.GtfsId))
- .Select(r => new RouteInfo
- {
- GtfsId = r.GtfsId,
- ShortName = _feedService.NormalizeRouteShortName(feedId, r.ShortName ?? ""),
- 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)],
- Usage = context.Usage
- });
-
+ return (stop, context);
}
private static int GetThresholdForFeed(string id)
diff --git a/src/Enmarcha.Backend/Controllers/TileController.cs b/src/Enmarcha.Backend/Controllers/TileController.cs
index ef71b67..300f9db 100644
--- a/src/Enmarcha.Backend/Controllers/TileController.cs
+++ b/src/Enmarcha.Backend/Controllers/TileController.cs
@@ -134,7 +134,7 @@ public class TileController : ControllerBase
features.Add(feature);
});
- foreach (var feature in features.OrderBy(f => f.Attributes["transitKind"] as string switch
+ foreach (var feature in features.OrderBy(f => (f.Attributes["transitKind"] as string) switch
{
"bus" => 3,
"train" => 2,
diff --git a/src/Enmarcha.Backend/Extensions/StopScheduleExtensions.cs b/src/Enmarcha.Backend/Extensions/StopScheduleExtensions.cs
deleted file mode 100644
index 1ef7990..0000000
--- a/src/Enmarcha.Backend/Extensions/StopScheduleExtensions.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using Enmarcha.Backend.Types;
-
-namespace Enmarcha.Backend.Extensions;
-
-public static class StopScheduleExtensions
-{
- public static DateTime? StartingDateTime(this StopArrivals.Types.ScheduledArrival stop, DateTime baseDate)
- {
- return ParseGtfsTime(stop.StartingTime, baseDate);
- }
-
- public static DateTime? CallingDateTime(this StopArrivals.Types.ScheduledArrival stop, DateTime baseDate)
- {
- return ParseGtfsTime(stop.CallingTime, baseDate);
- }
-
- /// <summary>
- /// Parse GTFS time format (HH:MM:SS) which can have hours >= 24 for services past midnight
- /// </summary>
- private static DateTime? ParseGtfsTime(string timeStr, DateTime baseDate)
- {
- if (string.IsNullOrWhiteSpace(timeStr))
- {
- return null;
- }
-
- var parts = timeStr.Split(':');
- if (parts.Length != 3)
- {
- return null;
- }
-
- if (!int.TryParse(parts[0], out var hours) ||
- !int.TryParse(parts[1], out var minutes) ||
- !int.TryParse(parts[2], out var seconds))
- {
- return null;
- }
-
- // Handle GTFS times that exceed 24 hours (e.g., 25:30:00 for 1:30 AM next day)
- var days = hours / 24;
- var normalizedHours = hours % 24;
-
- try
- {
- var dt = baseDate
- .AddDays(days)
- .AddHours(normalizedHours)
- .AddMinutes(minutes)
- .AddSeconds(seconds);
- return dt.AddSeconds(60 - dt.Second);
- }
- catch
- {
- return null;
- }
- }
-}
diff --git a/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs b/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs
index 6d8c2c0..9e44535 100644
--- a/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs
+++ b/src/Enmarcha.Backend/Services/ArrivalsPipeline.cs
@@ -20,6 +20,12 @@ public class ArrivalsContext
/// </summary>
public bool IsReduced { get; set; }
+ /// <summary>
+ /// Nano mode: skip all enrichment except real-time estimates.
+ /// Processors that populate shapes, marquee, next stops, and usage should no-op when true.
+ /// </summary>
+ public bool IsNano { get; set; }
+
public Position? StopLocation { get; set; }
public required List<Arrival> Arrivals { get; set; }
diff --git a/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs
index b933bf4..797221f 100644
--- a/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/CorunaRealTimeProcessor.cs
@@ -95,7 +95,6 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
if (stopLocation != null)
{
Position? currentPosition = null;
- int? stopShapeIndex = null;
if (arrival.RawOtpTrip is ArrivalsAtStopResponse.Arrival otpArrival &&
otpArrival.Trip.Geometry?.Points != null)
@@ -111,7 +110,6 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
var result = _shapeService.GetBusPosition(shape, stopLocation, meters);
currentPosition = result.BusPosition;
- stopShapeIndex = result.StopIndex;
if (currentPosition != null)
{
@@ -168,7 +166,6 @@ public class CorunaRealTimeProcessor : AbstractRealTimeProcessor
if (currentPosition != null)
{
arrival.CurrentPosition = currentPosition;
- arrival.StopShapeIndex = stopShapeIndex;
}
}
diff --git a/src/Enmarcha.Backend/Services/Processors/CtagShuttleRealTimeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/CtagShuttleRealTimeProcessor.cs
index 316591d..8f3e6db 100644
--- a/src/Enmarcha.Backend/Services/Processors/CtagShuttleRealTimeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/CtagShuttleRealTimeProcessor.cs
@@ -213,7 +213,6 @@ public class CtagShuttleRealTimeProcessor : AbstractRealTimeProcessor
}
activeArrival.CurrentPosition = shuttleWgs84;
- activeArrival.StopShapeIndex = stopPointIndex;
_logger.LogInformation(
"Updated active trip {TripId}: {Minutes} min (was {Scheduled} min, delay: {Delay} min, distance: {Distance:F1}m)",
diff --git a/src/Enmarcha.Backend/Services/Processors/FilterAndSortProcessor.cs b/src/Enmarcha.Backend/Services/Processors/FilterAndSortProcessor.cs
index 18ba2ac..cbcda93 100644
--- a/src/Enmarcha.Backend/Services/Processors/FilterAndSortProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/FilterAndSortProcessor.cs
@@ -29,8 +29,8 @@ public class FilterAndSortProcessor : IArrivalsProcessor
return a.Estimate.Minutes >= -10;
}).ToList();
- // 3. Limit results
- var limit = context.IsReduced ? 4 : 10;
+ // 3. Limit results — nano requests a full set so route/via filtering in the controller doesn't starve
+ var limit = context.IsNano ? 100 : context.IsReduced ? 4 : 10;
var limited = filtered.Take(limit).ToList();
// Update the context list in-place
diff --git a/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs
index 9e620c2..d5ff58e 100644
--- a/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/MarqueeProcessor.cs
@@ -11,6 +11,8 @@ public class MarqueeProcessor : IArrivalsProcessor
public Task ProcessAsync(ArrivalsContext context)
{
+ if (context.IsNano) return Task.CompletedTask;
+
var feedId = context.StopId.Split(':')[0];
foreach (var arrival in context.Arrivals)
diff --git a/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs b/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs
index 5d02066..5bdb5bb 100644
--- a/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/NextStopsProcessor.cs
@@ -13,6 +13,8 @@ public class NextStopsProcessor : IArrivalsProcessor
public Task ProcessAsync(ArrivalsContext context)
{
+ if (context.IsNano) return Task.CompletedTask;
+
var feedId = context.StopId.Split(':')[0];
foreach (var arrival in context.Arrivals)
diff --git a/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs
index f3af3a5..76f896f 100644
--- a/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs
@@ -13,7 +13,7 @@ public class ShapeProcessor : IArrivalsProcessor
public Task ProcessAsync(ArrivalsContext context)
{
- if (context.IsReduced)
+ if (context.IsReduced || context.IsNano)
{
return Task.CompletedTask;
}
diff --git a/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs b/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs
index 487d55a..52218d9 100644
--- a/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/VigoUsageProcessor.cs
@@ -25,7 +25,7 @@ public class VigoUsageProcessor : IArrivalsProcessor
public async Task ProcessAsync(ArrivalsContext context)
{
- if (!context.StopId.StartsWith("vitrasa:") || context.IsReduced) return;
+ if (!context.StopId.StartsWith("vitrasa:") || context.IsReduced || context.IsNano) return;
var normalizedCode = _feedService.NormalizeStopCode("vitrasa", context.StopCode);
diff --git a/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs
index e374f24..43152fd 100644
--- a/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs
+++ b/src/Enmarcha.Backend/Services/Processors/VitrasaRealTimeProcessor.cs
@@ -160,7 +160,6 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor
if (stopLocation != null)
{
Position? currentPosition = null;
- int? stopShapeIndex = null;
if (arrival.RawOtpTrip is ArrivalsAtStopResponse.Arrival otpArrival &&
otpArrival.Trip.Geometry?.Points != null)
@@ -176,7 +175,6 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor
var result = _shapeService.GetBusPosition(shape, stopLocation, meters);
currentPosition = result.BusPosition;
- stopShapeIndex = result.StopIndex;
// Populate Shape GeoJSON
if (!context.IsReduced && currentPosition != null)
@@ -228,7 +226,6 @@ public class VitrasaRealTimeProcessor : AbstractRealTimeProcessor
if (currentPosition != null)
{
arrival.CurrentPosition = currentPosition;
- arrival.StopShapeIndex = stopShapeIndex;
}
}
diff --git a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
index eab463d..9789d43 100644
--- a/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
+++ b/src/Enmarcha.Backend/Types/Arrivals/Arrival.cs
@@ -20,11 +20,12 @@ public class Arrival
[JsonPropertyName("currentPosition")] public Position? CurrentPosition { get; set; }
- [JsonPropertyName("stopShapeIndex")] public int? StopShapeIndex { get; set; }
[JsonPropertyName("vehicleInformation")]
public VehicleBadge? VehicleInformation { get; set; }
+ [JsonPropertyName("patternId")] public string? PatternId { get; set; }
+
[System.Text.Json.Serialization.JsonIgnore]
public List<string> NextStops { get; set; } = [];
diff --git a/src/Enmarcha.Backend/Types/Arrivals/StopEstimatesResponse.cs b/src/Enmarcha.Backend/Types/Arrivals/StopEstimatesResponse.cs
new file mode 100644
index 0000000..6d83587
--- /dev/null
+++ b/src/Enmarcha.Backend/Types/Arrivals/StopEstimatesResponse.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+
+namespace Enmarcha.Backend.Types.Arrivals;
+
+public class StopEstimatesResponse
+{
+ [JsonPropertyName("arrivals")] public List<ArrivalEstimate> Arrivals { get; set; } = [];
+}
+
+public class ArrivalEstimate
+{
+ [JsonPropertyName("tripId")] public required string TripId { get; set; }
+
+ [JsonPropertyName("patternId")] public string? PatternId { get; set; }
+
+ [JsonPropertyName("estimate")] public required ArrivalDetails Estimate { get; set; }
+
+ [JsonPropertyName("delay")] public DelayBadge? Delay { get; set; }
+}
diff --git a/src/Enmarcha.Backend/Types/ConsolidatedCirculation.cs b/src/Enmarcha.Backend/Types/ConsolidatedCirculation.cs
index 55d6e87..bb6a251 100644
--- a/src/Enmarcha.Backend/Types/ConsolidatedCirculation.cs
+++ b/src/Enmarcha.Backend/Types/ConsolidatedCirculation.cs
@@ -36,3 +36,15 @@ public class Position
public int OrientationDegrees { get; set; }
public int ShapeIndex { get; set; }
}
+
+public class Epsg25829
+{
+ public double X { get; set; }
+ public double Y { get; set; }
+}
+
+public class Shape
+{
+ public string ShapeId { get; set; }
+ public List<Epsg25829> Points { get; set; }
+}
diff --git a/src/Enmarcha.Backend/Types/StopSchedule.cs b/src/Enmarcha.Backend/Types/StopSchedule.cs
deleted file mode 100644
index 2a143ea..0000000
--- a/src/Enmarcha.Backend/Types/StopSchedule.cs
+++ /dev/null
@@ -1,1542 +0,0 @@
-// <auto-generated>
-// Generated by the protocol buffer compiler. DO NOT EDIT!
-// source: stop_schedule.proto
-// </auto-generated>
-#pragma warning disable 1591, 0612, 3021, 8981
-#region Designer generated code
-
-using pb = global::Google.Protobuf;
-using pbc = global::Google.Protobuf.Collections;
-using pbr = global::Google.Protobuf.Reflection;
-using scg = global::System.Collections.Generic;
-namespace Enmarcha.Backend.Types {
-
- /// <summary>Holder for reflection information generated from stop_schedule.proto</summary>
- public static partial class StopScheduleReflection {
-
- #region Descriptor
- /// <summary>File descriptor for stop_schedule.proto</summary>
- public static pbr::FileDescriptor Descriptor {
- get { return descriptor; }
- }
- private static pbr::FileDescriptor descriptor;
-
- static StopScheduleReflection() {
- byte[] descriptorData = global::System.Convert.FromBase64String(
- string.Concat(
- "ChNzdG9wX3NjaGVkdWxlLnByb3RvEgVwcm90byIhCglFcHNnMjU4MjkSCQoB",
- "eBgBIAEoARIJCgF5GAIgASgBIoMECgxTdG9wQXJyaXZhbHMSDwoHc3RvcF9p",
- "ZBgBIAEoCRIiCghsb2NhdGlvbhgDIAEoCzIQLnByb3RvLkVwc2cyNTgyORI2",
- "CghhcnJpdmFscxgFIAMoCzIkLnByb3RvLlN0b3BBcnJpdmFscy5TY2hlZHVs",
- "ZWRBcnJpdmFsGoUDChBTY2hlZHVsZWRBcnJpdmFsEhIKCnNlcnZpY2VfaWQY",
- "ASABKAkSDwoHdHJpcF9pZBgCIAEoCRIMCgRsaW5lGAMgASgJEg0KBXJvdXRl",
- "GAQgASgJEhAKCHNoYXBlX2lkGAUgASgJEhsKE3NoYXBlX2Rpc3RfdHJhdmVs",
- "ZWQYBiABKAESFQoNc3RvcF9zZXF1ZW5jZRgLIAEoDRIUCgxuZXh0X3N0cmVl",
- "dHMYDCADKAkSFQoNc3RhcnRpbmdfY29kZRgVIAEoCRIVCg1zdGFydGluZ19u",
- "YW1lGBYgASgJEhUKDXN0YXJ0aW5nX3RpbWUYFyABKAkSFAoMY2FsbGluZ190",
- "aW1lGCEgASgJEhMKC2NhbGxpbmdfc3NtGCIgASgNEhUKDXRlcm1pbnVzX2Nv",
- "ZGUYKSABKAkSFQoNdGVybWludXNfbmFtZRgqIAEoCRIVCg10ZXJtaW51c190",
- "aW1lGCsgASgJEh4KFnByZXZpb3VzX3RyaXBfc2hhcGVfaWQYMyABKAkiOwoF",
- "U2hhcGUSEAoIc2hhcGVfaWQYASABKAkSIAoGcG9pbnRzGAMgAygLMhAucHJv",
- "dG8uRXBzZzI1ODI5QiSqAiFDb3N0YXNkZXYuQnVzdXJiYW5vLkJhY2tlbmQu",
- "VHlwZXNiBnByb3RvMw=="));
- descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
- new pbr::FileDescriptor[] { },
- new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
- new pbr::GeneratedClrTypeInfo(typeof(global::Enmarcha.Backend.Types.Epsg25829), global::Enmarcha.Backend.Types.Epsg25829.Parser, new[]{ "X", "Y" }, null, null, null, null),
- new pbr::GeneratedClrTypeInfo(typeof(global::Enmarcha.Backend.Types.StopArrivals), global::Enmarcha.Backend.Types.StopArrivals.Parser, new[]{ "StopId", "Location", "Arrivals" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival), global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival.Parser, new[]{ "ServiceId", "TripId", "Line", "Route", "ShapeId", "ShapeDistTraveled", "StopSequence", "NextStreets", "StartingCode", "StartingName", "StartingTime", "CallingTime", "CallingSsm", "TerminusCode", "TerminusName", "TerminusTime", "PreviousTripShapeId" }, null, null, null, null)}),
- new pbr::GeneratedClrTypeInfo(typeof(global::Enmarcha.Backend.Types.Shape), global::Enmarcha.Backend.Types.Shape.Parser, new[]{ "ShapeId", "Points" }, null, null, null, null)
- }));
- }
- #endregion
-
- }
- #region Messages
- public sealed partial class Epsg25829 : pb::IMessage<Epsg25829>
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- , pb::IBufferMessage
- #endif
- {
- private static readonly pb::MessageParser<Epsg25829> _parser = new pb::MessageParser<Epsg25829>(() => new Epsg25829());
- private pb::UnknownFieldSet _unknownFields;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pb::MessageParser<Epsg25829> Parser { get { return _parser; } }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pbr::MessageDescriptor Descriptor {
- get { return global::Enmarcha.Backend.Types.StopScheduleReflection.Descriptor.MessageTypes[0]; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- pbr::MessageDescriptor pb::IMessage.Descriptor {
- get { return Descriptor; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public Epsg25829() {
- OnConstruction();
- }
-
- partial void OnConstruction();
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public Epsg25829(Epsg25829 other) : this() {
- x_ = other.x_;
- y_ = other.y_;
- _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public Epsg25829 Clone() {
- return new Epsg25829(this);
- }
-
- /// <summary>Field number for the "x" field.</summary>
- public const int XFieldNumber = 1;
- private double x_;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public double X {
- get { return x_; }
- set {
- x_ = value;
- }
- }
-
- /// <summary>Field number for the "y" field.</summary>
- public const int YFieldNumber = 2;
- private double y_;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public double Y {
- get { return y_; }
- set {
- y_ = value;
- }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override bool Equals(object other) {
- return Equals(other as Epsg25829);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public bool Equals(Epsg25829 other) {
- if (ReferenceEquals(other, null)) {
- return false;
- }
- if (ReferenceEquals(other, this)) {
- return true;
- }
- if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(X, other.X)) return false;
- if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(Y, other.Y)) return false;
- return Equals(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override int GetHashCode() {
- int hash = 1;
- if (X != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(X);
- if (Y != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Y);
- if (_unknownFields != null) {
- hash ^= _unknownFields.GetHashCode();
- }
- return hash;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override string ToString() {
- return pb::JsonFormatter.ToDiagnosticString(this);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void WriteTo(pb::CodedOutputStream output) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- output.WriteRawMessage(this);
- #else
- if (X != 0D) {
- output.WriteRawTag(9);
- output.WriteDouble(X);
- }
- if (Y != 0D) {
- output.WriteRawTag(17);
- output.WriteDouble(Y);
- }
- if (_unknownFields != null) {
- _unknownFields.WriteTo(output);
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
- if (X != 0D) {
- output.WriteRawTag(9);
- output.WriteDouble(X);
- }
- if (Y != 0D) {
- output.WriteRawTag(17);
- output.WriteDouble(Y);
- }
- if (_unknownFields != null) {
- _unknownFields.WriteTo(ref output);
- }
- }
- #endif
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public int CalculateSize() {
- int size = 0;
- if (X != 0D) {
- size += 1 + 8;
- }
- if (Y != 0D) {
- size += 1 + 8;
- }
- if (_unknownFields != null) {
- size += _unknownFields.CalculateSize();
- }
- return size;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(Epsg25829 other) {
- if (other == null) {
- return;
- }
- if (other.X != 0D) {
- X = other.X;
- }
- if (other.Y != 0D) {
- Y = other.Y;
- }
- _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(pb::CodedInputStream input) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- input.ReadRawMessage(this);
- #else
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
- break;
- case 9: {
- X = input.ReadDouble();
- break;
- }
- case 17: {
- Y = input.ReadDouble();
- break;
- }
- }
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
- break;
- case 9: {
- X = input.ReadDouble();
- break;
- }
- case 17: {
- Y = input.ReadDouble();
- break;
- }
- }
- }
- }
- #endif
-
- }
-
- public sealed partial class StopArrivals : pb::IMessage<StopArrivals>
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- , pb::IBufferMessage
- #endif
- {
- private static readonly pb::MessageParser<StopArrivals> _parser = new pb::MessageParser<StopArrivals>(() => new StopArrivals());
- private pb::UnknownFieldSet _unknownFields;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pb::MessageParser<StopArrivals> Parser { get { return _parser; } }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pbr::MessageDescriptor Descriptor {
- get { return global::Enmarcha.Backend.Types.StopScheduleReflection.Descriptor.MessageTypes[1]; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- pbr::MessageDescriptor pb::IMessage.Descriptor {
- get { return Descriptor; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public StopArrivals() {
- OnConstruction();
- }
-
- partial void OnConstruction();
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public StopArrivals(StopArrivals other) : this() {
- stopId_ = other.stopId_;
- location_ = other.location_ != null ? other.location_.Clone() : null;
- arrivals_ = other.arrivals_.Clone();
- _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public StopArrivals Clone() {
- return new StopArrivals(this);
- }
-
- /// <summary>Field number for the "stop_id" field.</summary>
- public const int StopIdFieldNumber = 1;
- private string stopId_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string StopId {
- get { return stopId_; }
- set {
- stopId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "location" field.</summary>
- public const int LocationFieldNumber = 3;
- private global::Enmarcha.Backend.Types.Epsg25829 location_;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public global::Enmarcha.Backend.Types.Epsg25829 Location {
- get { return location_; }
- set {
- location_ = value;
- }
- }
-
- /// <summary>Field number for the "arrivals" field.</summary>
- public const int ArrivalsFieldNumber = 5;
- private static readonly pb::FieldCodec<global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival> _repeated_arrivals_codec
- = pb::FieldCodec.ForMessage(42, global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival.Parser);
- private readonly pbc::RepeatedField<global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival> arrivals_ = new pbc::RepeatedField<global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival>();
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public pbc::RepeatedField<global::Enmarcha.Backend.Types.StopArrivals.Types.ScheduledArrival> Arrivals {
- get { return arrivals_; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override bool Equals(object other) {
- return Equals(other as StopArrivals);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public bool Equals(StopArrivals other) {
- if (ReferenceEquals(other, null)) {
- return false;
- }
- if (ReferenceEquals(other, this)) {
- return true;
- }
- if (StopId != other.StopId) return false;
- if (!object.Equals(Location, other.Location)) return false;
- if(!arrivals_.Equals(other.arrivals_)) return false;
- return Equals(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override int GetHashCode() {
- int hash = 1;
- if (StopId.Length != 0) hash ^= StopId.GetHashCode();
- if (location_ != null) hash ^= Location.GetHashCode();
- hash ^= arrivals_.GetHashCode();
- if (_unknownFields != null) {
- hash ^= _unknownFields.GetHashCode();
- }
- return hash;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override string ToString() {
- return pb::JsonFormatter.ToDiagnosticString(this);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void WriteTo(pb::CodedOutputStream output) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- output.WriteRawMessage(this);
- #else
- if (StopId.Length != 0) {
- output.WriteRawTag(10);
- output.WriteString(StopId);
- }
- if (location_ != null) {
- output.WriteRawTag(26);
- output.WriteMessage(Location);
- }
- arrivals_.WriteTo(output, _repeated_arrivals_codec);
- if (_unknownFields != null) {
- _unknownFields.WriteTo(output);
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
- if (StopId.Length != 0) {
- output.WriteRawTag(10);
- output.WriteString(StopId);
- }
- if (location_ != null) {
- output.WriteRawTag(26);
- output.WriteMessage(Location);
- }
- arrivals_.WriteTo(ref output, _repeated_arrivals_codec);
- if (_unknownFields != null) {
- _unknownFields.WriteTo(ref output);
- }
- }
- #endif
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public int CalculateSize() {
- int size = 0;
- if (StopId.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(StopId);
- }
- if (location_ != null) {
- size += 1 + pb::CodedOutputStream.ComputeMessageSize(Location);
- }
- size += arrivals_.CalculateSize(_repeated_arrivals_codec);
- if (_unknownFields != null) {
- size += _unknownFields.CalculateSize();
- }
- return size;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(StopArrivals other) {
- if (other == null) {
- return;
- }
- if (other.StopId.Length != 0) {
- StopId = other.StopId;
- }
- if (other.location_ != null) {
- if (location_ == null) {
- Location = new global::Enmarcha.Backend.Types.Epsg25829();
- }
- Location.MergeFrom(other.Location);
- }
- arrivals_.Add(other.arrivals_);
- _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(pb::CodedInputStream input) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- input.ReadRawMessage(this);
- #else
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
- break;
- case 10: {
- StopId = input.ReadString();
- break;
- }
- case 26: {
- if (location_ == null) {
- Location = new global::Enmarcha.Backend.Types.Epsg25829();
- }
- input.ReadMessage(Location);
- break;
- }
- case 42: {
- arrivals_.AddEntriesFrom(input, _repeated_arrivals_codec);
- break;
- }
- }
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
- break;
- case 10: {
- StopId = input.ReadString();
- break;
- }
- case 26: {
- if (location_ == null) {
- Location = new global::Enmarcha.Backend.Types.Epsg25829();
- }
- input.ReadMessage(Location);
- break;
- }
- case 42: {
- arrivals_.AddEntriesFrom(ref input, _repeated_arrivals_codec);
- break;
- }
- }
- }
- }
- #endif
-
- #region Nested types
- /// <summary>Container for nested types declared in the StopArrivals message type.</summary>
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static partial class Types {
- public sealed partial class ScheduledArrival : pb::IMessage<ScheduledArrival>
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- , pb::IBufferMessage
- #endif
- {
- private static readonly pb::MessageParser<ScheduledArrival> _parser = new pb::MessageParser<ScheduledArrival>(() => new ScheduledArrival());
- private pb::UnknownFieldSet _unknownFields;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pb::MessageParser<ScheduledArrival> Parser { get { return _parser; } }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pbr::MessageDescriptor Descriptor {
- get { return global::Enmarcha.Backend.Types.StopArrivals.Descriptor.NestedTypes[0]; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- pbr::MessageDescriptor pb::IMessage.Descriptor {
- get { return Descriptor; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public ScheduledArrival() {
- OnConstruction();
- }
-
- partial void OnConstruction();
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public ScheduledArrival(ScheduledArrival other) : this() {
- serviceId_ = other.serviceId_;
- tripId_ = other.tripId_;
- line_ = other.line_;
- route_ = other.route_;
- shapeId_ = other.shapeId_;
- shapeDistTraveled_ = other.shapeDistTraveled_;
- stopSequence_ = other.stopSequence_;
- nextStreets_ = other.nextStreets_.Clone();
- startingCode_ = other.startingCode_;
- startingName_ = other.startingName_;
- startingTime_ = other.startingTime_;
- callingTime_ = other.callingTime_;
- callingSsm_ = other.callingSsm_;
- terminusCode_ = other.terminusCode_;
- terminusName_ = other.terminusName_;
- terminusTime_ = other.terminusTime_;
- previousTripShapeId_ = other.previousTripShapeId_;
- _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public ScheduledArrival Clone() {
- return new ScheduledArrival(this);
- }
-
- /// <summary>Field number for the "service_id" field.</summary>
- public const int ServiceIdFieldNumber = 1;
- private string serviceId_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string ServiceId {
- get { return serviceId_; }
- set {
- serviceId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "trip_id" field.</summary>
- public const int TripIdFieldNumber = 2;
- private string tripId_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string TripId {
- get { return tripId_; }
- set {
- tripId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "line" field.</summary>
- public const int LineFieldNumber = 3;
- private string line_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string Line {
- get { return line_; }
- set {
- line_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "route" field.</summary>
- public const int RouteFieldNumber = 4;
- private string route_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string Route {
- get { return route_; }
- set {
- route_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "shape_id" field.</summary>
- public const int ShapeIdFieldNumber = 5;
- private string shapeId_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string ShapeId {
- get { return shapeId_; }
- set {
- shapeId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "shape_dist_traveled" field.</summary>
- public const int ShapeDistTraveledFieldNumber = 6;
- private double shapeDistTraveled_;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public double ShapeDistTraveled {
- get { return shapeDistTraveled_; }
- set {
- shapeDistTraveled_ = value;
- }
- }
-
- /// <summary>Field number for the "stop_sequence" field.</summary>
- public const int StopSequenceFieldNumber = 11;
- private uint stopSequence_;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public uint StopSequence {
- get { return stopSequence_; }
- set {
- stopSequence_ = value;
- }
- }
-
- /// <summary>Field number for the "next_streets" field.</summary>
- public const int NextStreetsFieldNumber = 12;
- private static readonly pb::FieldCodec<string> _repeated_nextStreets_codec
- = pb::FieldCodec.ForString(98);
- private readonly pbc::RepeatedField<string> nextStreets_ = new pbc::RepeatedField<string>();
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public pbc::RepeatedField<string> NextStreets {
- get { return nextStreets_; }
- }
-
- /// <summary>Field number for the "starting_code" field.</summary>
- public const int StartingCodeFieldNumber = 21;
- private string startingCode_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string StartingCode {
- get { return startingCode_; }
- set {
- startingCode_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "starting_name" field.</summary>
- public const int StartingNameFieldNumber = 22;
- private string startingName_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string StartingName {
- get { return startingName_; }
- set {
- startingName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "starting_time" field.</summary>
- public const int StartingTimeFieldNumber = 23;
- private string startingTime_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string StartingTime {
- get { return startingTime_; }
- set {
- startingTime_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "calling_time" field.</summary>
- public const int CallingTimeFieldNumber = 33;
- private string callingTime_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string CallingTime {
- get { return callingTime_; }
- set {
- callingTime_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "calling_ssm" field.</summary>
- public const int CallingSsmFieldNumber = 34;
- private uint callingSsm_;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public uint CallingSsm {
- get { return callingSsm_; }
- set {
- callingSsm_ = value;
- }
- }
-
- /// <summary>Field number for the "terminus_code" field.</summary>
- public const int TerminusCodeFieldNumber = 41;
- private string terminusCode_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string TerminusCode {
- get { return terminusCode_; }
- set {
- terminusCode_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "terminus_name" field.</summary>
- public const int TerminusNameFieldNumber = 42;
- private string terminusName_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string TerminusName {
- get { return terminusName_; }
- set {
- terminusName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "terminus_time" field.</summary>
- public const int TerminusTimeFieldNumber = 43;
- private string terminusTime_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string TerminusTime {
- get { return terminusTime_; }
- set {
- terminusTime_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "previous_trip_shape_id" field.</summary>
- public const int PreviousTripShapeIdFieldNumber = 51;
- private string previousTripShapeId_ = "";
- /// <summary>
- /// Shape ID of the previous trip when the bus comes from another trip that ends at the starting point
- /// </summary>
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string PreviousTripShapeId {
- get { return previousTripShapeId_; }
- set {
- previousTripShapeId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override bool Equals(object other) {
- return Equals(other as ScheduledArrival);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public bool Equals(ScheduledArrival other) {
- if (ReferenceEquals(other, null)) {
- return false;
- }
- if (ReferenceEquals(other, this)) {
- return true;
- }
- if (ServiceId != other.ServiceId) return false;
- if (TripId != other.TripId) return false;
- if (Line != other.Line) return false;
- if (Route != other.Route) return false;
- if (ShapeId != other.ShapeId) return false;
- if (!pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.Equals(ShapeDistTraveled, other.ShapeDistTraveled)) return false;
- if (StopSequence != other.StopSequence) return false;
- if(!nextStreets_.Equals(other.nextStreets_)) return false;
- if (StartingCode != other.StartingCode) return false;
- if (StartingName != other.StartingName) return false;
- if (StartingTime != other.StartingTime) return false;
- if (CallingTime != other.CallingTime) return false;
- if (CallingSsm != other.CallingSsm) return false;
- if (TerminusCode != other.TerminusCode) return false;
- if (TerminusName != other.TerminusName) return false;
- if (TerminusTime != other.TerminusTime) return false;
- if (PreviousTripShapeId != other.PreviousTripShapeId) return false;
- return Equals(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override int GetHashCode() {
- int hash = 1;
- if (ServiceId.Length != 0) hash ^= ServiceId.GetHashCode();
- if (TripId.Length != 0) hash ^= TripId.GetHashCode();
- if (Line.Length != 0) hash ^= Line.GetHashCode();
- if (Route.Length != 0) hash ^= Route.GetHashCode();
- if (ShapeId.Length != 0) hash ^= ShapeId.GetHashCode();
- if (ShapeDistTraveled != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(ShapeDistTraveled);
- if (StopSequence != 0) hash ^= StopSequence.GetHashCode();
- hash ^= nextStreets_.GetHashCode();
- if (StartingCode.Length != 0) hash ^= StartingCode.GetHashCode();
- if (StartingName.Length != 0) hash ^= StartingName.GetHashCode();
- if (StartingTime.Length != 0) hash ^= StartingTime.GetHashCode();
- if (CallingTime.Length != 0) hash ^= CallingTime.GetHashCode();
- if (CallingSsm != 0) hash ^= CallingSsm.GetHashCode();
- if (TerminusCode.Length != 0) hash ^= TerminusCode.GetHashCode();
- if (TerminusName.Length != 0) hash ^= TerminusName.GetHashCode();
- if (TerminusTime.Length != 0) hash ^= TerminusTime.GetHashCode();
- if (PreviousTripShapeId.Length != 0) hash ^= PreviousTripShapeId.GetHashCode();
- if (_unknownFields != null) {
- hash ^= _unknownFields.GetHashCode();
- }
- return hash;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override string ToString() {
- return pb::JsonFormatter.ToDiagnosticString(this);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void WriteTo(pb::CodedOutputStream output) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- output.WriteRawMessage(this);
- #else
- if (ServiceId.Length != 0) {
- output.WriteRawTag(10);
- output.WriteString(ServiceId);
- }
- if (TripId.Length != 0) {
- output.WriteRawTag(18);
- output.WriteString(TripId);
- }
- if (Line.Length != 0) {
- output.WriteRawTag(26);
- output.WriteString(Line);
- }
- if (Route.Length != 0) {
- output.WriteRawTag(34);
- output.WriteString(Route);
- }
- if (ShapeId.Length != 0) {
- output.WriteRawTag(42);
- output.WriteString(ShapeId);
- }
- if (ShapeDistTraveled != 0D) {
- output.WriteRawTag(49);
- output.WriteDouble(ShapeDistTraveled);
- }
- if (StopSequence != 0) {
- output.WriteRawTag(88);
- output.WriteUInt32(StopSequence);
- }
- nextStreets_.WriteTo(output, _repeated_nextStreets_codec);
- if (StartingCode.Length != 0) {
- output.WriteRawTag(170, 1);
- output.WriteString(StartingCode);
- }
- if (StartingName.Length != 0) {
- output.WriteRawTag(178, 1);
- output.WriteString(StartingName);
- }
- if (StartingTime.Length != 0) {
- output.WriteRawTag(186, 1);
- output.WriteString(StartingTime);
- }
- if (CallingTime.Length != 0) {
- output.WriteRawTag(138, 2);
- output.WriteString(CallingTime);
- }
- if (CallingSsm != 0) {
- output.WriteRawTag(144, 2);
- output.WriteUInt32(CallingSsm);
- }
- if (TerminusCode.Length != 0) {
- output.WriteRawTag(202, 2);
- output.WriteString(TerminusCode);
- }
- if (TerminusName.Length != 0) {
- output.WriteRawTag(210, 2);
- output.WriteString(TerminusName);
- }
- if (TerminusTime.Length != 0) {
- output.WriteRawTag(218, 2);
- output.WriteString(TerminusTime);
- }
- if (PreviousTripShapeId.Length != 0) {
- output.WriteRawTag(154, 3);
- output.WriteString(PreviousTripShapeId);
- }
- if (_unknownFields != null) {
- _unknownFields.WriteTo(output);
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
- if (ServiceId.Length != 0) {
- output.WriteRawTag(10);
- output.WriteString(ServiceId);
- }
- if (TripId.Length != 0) {
- output.WriteRawTag(18);
- output.WriteString(TripId);
- }
- if (Line.Length != 0) {
- output.WriteRawTag(26);
- output.WriteString(Line);
- }
- if (Route.Length != 0) {
- output.WriteRawTag(34);
- output.WriteString(Route);
- }
- if (ShapeId.Length != 0) {
- output.WriteRawTag(42);
- output.WriteString(ShapeId);
- }
- if (ShapeDistTraveled != 0D) {
- output.WriteRawTag(49);
- output.WriteDouble(ShapeDistTraveled);
- }
- if (StopSequence != 0) {
- output.WriteRawTag(88);
- output.WriteUInt32(StopSequence);
- }
- nextStreets_.WriteTo(ref output, _repeated_nextStreets_codec);
- if (StartingCode.Length != 0) {
- output.WriteRawTag(170, 1);
- output.WriteString(StartingCode);
- }
- if (StartingName.Length != 0) {
- output.WriteRawTag(178, 1);
- output.WriteString(StartingName);
- }
- if (StartingTime.Length != 0) {
- output.WriteRawTag(186, 1);
- output.WriteString(StartingTime);
- }
- if (CallingTime.Length != 0) {
- output.WriteRawTag(138, 2);
- output.WriteString(CallingTime);
- }
- if (CallingSsm != 0) {
- output.WriteRawTag(144, 2);
- output.WriteUInt32(CallingSsm);
- }
- if (TerminusCode.Length != 0) {
- output.WriteRawTag(202, 2);
- output.WriteString(TerminusCode);
- }
- if (TerminusName.Length != 0) {
- output.WriteRawTag(210, 2);
- output.WriteString(TerminusName);
- }
- if (TerminusTime.Length != 0) {
- output.WriteRawTag(218, 2);
- output.WriteString(TerminusTime);
- }
- if (PreviousTripShapeId.Length != 0) {
- output.WriteRawTag(154, 3);
- output.WriteString(PreviousTripShapeId);
- }
- if (_unknownFields != null) {
- _unknownFields.WriteTo(ref output);
- }
- }
- #endif
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public int CalculateSize() {
- int size = 0;
- if (ServiceId.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(ServiceId);
- }
- if (TripId.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(TripId);
- }
- if (Line.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(Line);
- }
- if (Route.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(Route);
- }
- if (ShapeId.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(ShapeId);
- }
- if (ShapeDistTraveled != 0D) {
- size += 1 + 8;
- }
- if (StopSequence != 0) {
- size += 1 + pb::CodedOutputStream.ComputeUInt32Size(StopSequence);
- }
- size += nextStreets_.CalculateSize(_repeated_nextStreets_codec);
- if (StartingCode.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(StartingCode);
- }
- if (StartingName.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(StartingName);
- }
- if (StartingTime.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(StartingTime);
- }
- if (CallingTime.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(CallingTime);
- }
- if (CallingSsm != 0) {
- size += 2 + pb::CodedOutputStream.ComputeUInt32Size(CallingSsm);
- }
- if (TerminusCode.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(TerminusCode);
- }
- if (TerminusName.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(TerminusName);
- }
- if (TerminusTime.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(TerminusTime);
- }
- if (PreviousTripShapeId.Length != 0) {
- size += 2 + pb::CodedOutputStream.ComputeStringSize(PreviousTripShapeId);
- }
- if (_unknownFields != null) {
- size += _unknownFields.CalculateSize();
- }
- return size;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(ScheduledArrival other) {
- if (other == null) {
- return;
- }
- if (other.ServiceId.Length != 0) {
- ServiceId = other.ServiceId;
- }
- if (other.TripId.Length != 0) {
- TripId = other.TripId;
- }
- if (other.Line.Length != 0) {
- Line = other.Line;
- }
- if (other.Route.Length != 0) {
- Route = other.Route;
- }
- if (other.ShapeId.Length != 0) {
- ShapeId = other.ShapeId;
- }
- if (other.ShapeDistTraveled != 0D) {
- ShapeDistTraveled = other.ShapeDistTraveled;
- }
- if (other.StopSequence != 0) {
- StopSequence = other.StopSequence;
- }
- nextStreets_.Add(other.nextStreets_);
- if (other.StartingCode.Length != 0) {
- StartingCode = other.StartingCode;
- }
- if (other.StartingName.Length != 0) {
- StartingName = other.StartingName;
- }
- if (other.StartingTime.Length != 0) {
- StartingTime = other.StartingTime;
- }
- if (other.CallingTime.Length != 0) {
- CallingTime = other.CallingTime;
- }
- if (other.CallingSsm != 0) {
- CallingSsm = other.CallingSsm;
- }
- if (other.TerminusCode.Length != 0) {
- TerminusCode = other.TerminusCode;
- }
- if (other.TerminusName.Length != 0) {
- TerminusName = other.TerminusName;
- }
- if (other.TerminusTime.Length != 0) {
- TerminusTime = other.TerminusTime;
- }
- if (other.PreviousTripShapeId.Length != 0) {
- PreviousTripShapeId = other.PreviousTripShapeId;
- }
- _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(pb::CodedInputStream input) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- input.ReadRawMessage(this);
- #else
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
- break;
- case 10: {
- ServiceId = input.ReadString();
- break;
- }
- case 18: {
- TripId = input.ReadString();
- break;
- }
- case 26: {
- Line = input.ReadString();
- break;
- }
- case 34: {
- Route = input.ReadString();
- break;
- }
- case 42: {
- ShapeId = input.ReadString();
- break;
- }
- case 49: {
- ShapeDistTraveled = input.ReadDouble();
- break;
- }
- case 88: {
- StopSequence = input.ReadUInt32();
- break;
- }
- case 98: {
- nextStreets_.AddEntriesFrom(input, _repeated_nextStreets_codec);
- break;
- }
- case 170: {
- StartingCode = input.ReadString();
- break;
- }
- case 178: {
- StartingName = input.ReadString();
- break;
- }
- case 186: {
- StartingTime = input.ReadString();
- break;
- }
- case 266: {
- CallingTime = input.ReadString();
- break;
- }
- case 272: {
- CallingSsm = input.ReadUInt32();
- break;
- }
- case 330: {
- TerminusCode = input.ReadString();
- break;
- }
- case 338: {
- TerminusName = input.ReadString();
- break;
- }
- case 346: {
- TerminusTime = input.ReadString();
- break;
- }
- case 410: {
- PreviousTripShapeId = input.ReadString();
- break;
- }
- }
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
- break;
- case 10: {
- ServiceId = input.ReadString();
- break;
- }
- case 18: {
- TripId = input.ReadString();
- break;
- }
- case 26: {
- Line = input.ReadString();
- break;
- }
- case 34: {
- Route = input.ReadString();
- break;
- }
- case 42: {
- ShapeId = input.ReadString();
- break;
- }
- case 49: {
- ShapeDistTraveled = input.ReadDouble();
- break;
- }
- case 88: {
- StopSequence = input.ReadUInt32();
- break;
- }
- case 98: {
- nextStreets_.AddEntriesFrom(ref input, _repeated_nextStreets_codec);
- break;
- }
- case 170: {
- StartingCode = input.ReadString();
- break;
- }
- case 178: {
- StartingName = input.ReadString();
- break;
- }
- case 186: {
- StartingTime = input.ReadString();
- break;
- }
- case 266: {
- CallingTime = input.ReadString();
- break;
- }
- case 272: {
- CallingSsm = input.ReadUInt32();
- break;
- }
- case 330: {
- TerminusCode = input.ReadString();
- break;
- }
- case 338: {
- TerminusName = input.ReadString();
- break;
- }
- case 346: {
- TerminusTime = input.ReadString();
- break;
- }
- case 410: {
- PreviousTripShapeId = input.ReadString();
- break;
- }
- }
- }
- }
- #endif
-
- }
-
- }
- #endregion
-
- }
-
- public sealed partial class Shape : pb::IMessage<Shape>
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- , pb::IBufferMessage
- #endif
- {
- private static readonly pb::MessageParser<Shape> _parser = new pb::MessageParser<Shape>(() => new Shape());
- private pb::UnknownFieldSet _unknownFields;
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pb::MessageParser<Shape> Parser { get { return _parser; } }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public static pbr::MessageDescriptor Descriptor {
- get { return global::Enmarcha.Backend.Types.StopScheduleReflection.Descriptor.MessageTypes[2]; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- pbr::MessageDescriptor pb::IMessage.Descriptor {
- get { return Descriptor; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public Shape() {
- OnConstruction();
- }
-
- partial void OnConstruction();
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public Shape(Shape other) : this() {
- shapeId_ = other.shapeId_;
- points_ = other.points_.Clone();
- _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public Shape Clone() {
- return new Shape(this);
- }
-
- /// <summary>Field number for the "shape_id" field.</summary>
- public const int ShapeIdFieldNumber = 1;
- private string shapeId_ = "";
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public string ShapeId {
- get { return shapeId_; }
- set {
- shapeId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
- }
- }
-
- /// <summary>Field number for the "points" field.</summary>
- public const int PointsFieldNumber = 3;
- private static readonly pb::FieldCodec<global::Enmarcha.Backend.Types.Epsg25829> _repeated_points_codec
- = pb::FieldCodec.ForMessage(26, global::Enmarcha.Backend.Types.Epsg25829.Parser);
- private readonly pbc::RepeatedField<global::Enmarcha.Backend.Types.Epsg25829> points_ = new pbc::RepeatedField<global::Enmarcha.Backend.Types.Epsg25829>();
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public pbc::RepeatedField<global::Enmarcha.Backend.Types.Epsg25829> Points {
- get { return points_; }
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override bool Equals(object other) {
- return Equals(other as Shape);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public bool Equals(Shape other) {
- if (ReferenceEquals(other, null)) {
- return false;
- }
- if (ReferenceEquals(other, this)) {
- return true;
- }
- if (ShapeId != other.ShapeId) return false;
- if(!points_.Equals(other.points_)) return false;
- return Equals(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override int GetHashCode() {
- int hash = 1;
- if (ShapeId.Length != 0) hash ^= ShapeId.GetHashCode();
- hash ^= points_.GetHashCode();
- if (_unknownFields != null) {
- hash ^= _unknownFields.GetHashCode();
- }
- return hash;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public override string ToString() {
- return pb::JsonFormatter.ToDiagnosticString(this);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void WriteTo(pb::CodedOutputStream output) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- output.WriteRawMessage(this);
- #else
- if (ShapeId.Length != 0) {
- output.WriteRawTag(10);
- output.WriteString(ShapeId);
- }
- points_.WriteTo(output, _repeated_points_codec);
- if (_unknownFields != null) {
- _unknownFields.WriteTo(output);
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
- if (ShapeId.Length != 0) {
- output.WriteRawTag(10);
- output.WriteString(ShapeId);
- }
- points_.WriteTo(ref output, _repeated_points_codec);
- if (_unknownFields != null) {
- _unknownFields.WriteTo(ref output);
- }
- }
- #endif
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public int CalculateSize() {
- int size = 0;
- if (ShapeId.Length != 0) {
- size += 1 + pb::CodedOutputStream.ComputeStringSize(ShapeId);
- }
- size += points_.CalculateSize(_repeated_points_codec);
- if (_unknownFields != null) {
- size += _unknownFields.CalculateSize();
- }
- return size;
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(Shape other) {
- if (other == null) {
- return;
- }
- if (other.ShapeId.Length != 0) {
- ShapeId = other.ShapeId;
- }
- points_.Add(other.points_);
- _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
- }
-
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- public void MergeFrom(pb::CodedInputStream input) {
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- input.ReadRawMessage(this);
- #else
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
- break;
- case 10: {
- ShapeId = input.ReadString();
- break;
- }
- case 26: {
- points_.AddEntriesFrom(input, _repeated_points_codec);
- break;
- }
- }
- }
- #endif
- }
-
- #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
- [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
- void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
- uint tag;
- while ((tag = input.ReadTag()) != 0) {
- switch(tag) {
- default:
- _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
- break;
- case 10: {
- ShapeId = input.ReadString();
- break;
- }
- case 26: {
- points_.AddEntriesFrom(ref input, _repeated_points_codec);
- break;
- }
- }
- }
- }
- #endif
-
- }
-
- #endregion
-
-}
-
-#endregion Designer generated code
diff --git a/src/Enmarcha.Backend/Types/VigoSchedules.cs b/src/Enmarcha.Backend/Types/VigoSchedules.cs
deleted file mode 100644
index ee7930f..0000000
--- a/src/Enmarcha.Backend/Types/VigoSchedules.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace Enmarcha.Backend.Types;
-
-public class ScheduledStop
-{
- [JsonPropertyName("trip_id")] public required string TripId { get; set; }
- [JsonPropertyName("service_id")] public required string ServiceId { get; set; }
- [JsonPropertyName("line")] public required string Line { get; set; }
- [JsonPropertyName("route")] public required string Route { get; set; }
- [JsonPropertyName("stop_sequence")] public required int StopSequence { get; set; }
-
- [JsonPropertyName("shape_dist_traveled")]
- public required double ShapeDistTraveled { get; set; }
-
- [JsonPropertyName("next_streets")] public required string[] NextStreets { get; set; }
-
- [JsonPropertyName("starting_code")] public required string StartingCode { get; set; }
- [JsonPropertyName("starting_name")] public required string StartingName { get; set; }
- [JsonPropertyName("starting_time")] public required string StartingTime { get; set; }
- public DateTime? StartingDateTime(DateTime? baseDate = null)
- {
- return ParseGtfsTime(StartingTime, baseDate);
- }
-
- [JsonPropertyName("calling_ssm")] public required int CallingSsm { get; set; }
- [JsonPropertyName("calling_time")] public required string CallingTime { get; set; }
- public DateTime? CallingDateTime(DateTime? baseDate = null)
- {
- return ParseGtfsTime(CallingTime, baseDate);
- }
-
- [JsonPropertyName("terminus_code")] public required string TerminusCode { get; set; }
- [JsonPropertyName("terminus_name")] public required string TerminusName { get; set; }
- [JsonPropertyName("terminus_time")] public required string TerminusTime { get; set; }
-
- /// <summary>
- /// Parse GTFS time format (HH:MM:SS) which can have hours >= 24 for services past midnight
- /// </summary>
- private static DateTime? ParseGtfsTime(string timeStr, DateTime? baseDate = null)
- {
- if (string.IsNullOrWhiteSpace(timeStr))
- {
- return null;
- }
-
- var parts = timeStr.Split(':');
- if (parts.Length != 3)
- {
- return null;
- }
-
- if (!int.TryParse(parts[0], out var hours) ||
- !int.TryParse(parts[1], out var minutes) ||
- !int.TryParse(parts[2], out var seconds))
- {
- return null;
- }
-
- // Handle GTFS times that exceed 24 hours (e.g., 25:30:00 for 1:30 AM next day)
- var days = hours / 24;
- var normalizedHours = hours % 24;
-
- try
- {
- var dt = (baseDate ?? DateTime.Today)
- .AddDays(days)
- .AddHours(normalizedHours)
- .AddMinutes(minutes)
- .AddSeconds(seconds);
- return dt.AddSeconds(60 - dt.Second);
- }
- catch
- {
- return null;
- }
- }
-}
diff --git a/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs b/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs
index 64e148d..6e89f08 100644
--- a/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs
+++ b/src/Enmarcha.Sources.OpenTripPlannerGql/Queries/ArrivalsAtStop.cs
@@ -53,8 +53,12 @@ public class ArrivalsAtStopContent : IGraphRequest<ArrivalsAtStopContent.Args>
}}
}}
{geometryField}
+ pattern {{
+ id
+ }}
stoptimes {{
stop {{
+ gtfsId
name
lat
lon
@@ -125,9 +129,16 @@ public class ArrivalsAtStopResponse : AbstractGraphResponse
[JsonPropertyName("tripGeometry")] public GeometryDetails? Geometry { get; set; }
+ [JsonPropertyName("pattern")] public PatternRef? Pattern { get; set; }
+
[JsonPropertyName("stoptimes")] public List<StoptimeDetails> Stoptimes { get; set; } = [];
}
+ public class PatternRef
+ {
+ [JsonPropertyName("id")] public required string Id { get; set; }
+ }
+
public class GeometryDetails
{
[JsonPropertyName("points")] public string? Points { get; set; }
@@ -141,6 +152,7 @@ public class ArrivalsAtStopResponse : AbstractGraphResponse
public class StopDetails
{
+ [JsonPropertyName("gtfsId")] public string? GtfsId { get; set; }
[JsonPropertyName("name")] public required string Name { get; set; }
[JsonPropertyName("lat")] public double Lat { get; set; }
[JsonPropertyName("lon")] public double Lon { get; set; }
diff --git a/src/frontend/app/api/arrivals.ts b/src/frontend/app/api/arrivals.ts
index 8ae6e78..ad99630 100644
--- a/src/frontend/app/api/arrivals.ts
+++ b/src/frontend/app/api/arrivals.ts
@@ -1,6 +1,8 @@
import {
StopArrivalsResponseSchema,
+ StopEstimatesResponseSchema,
type StopArrivalsResponse,
+ type StopEstimatesResponse,
} from "./schema";
export const fetchArrivals = async (
@@ -29,3 +31,31 @@ export const fetchArrivals = async (
throw e;
}
};
+
+export const fetchEstimates = async (
+ stopId: string,
+ routeId: string,
+ viaStopId?: string
+): Promise<StopEstimatesResponse> => {
+ let url = `/api/stops/estimates?stop=${encodeURIComponent(stopId)}&route=${encodeURIComponent(routeId)}`;
+ if (viaStopId) {
+ url += `&via=${encodeURIComponent(viaStopId)}`;
+ }
+
+ const resp = await fetch(url, {
+ headers: { Accept: "application/json" },
+ });
+
+ if (!resp.ok) {
+ throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
+ }
+
+ const data = await resp.json();
+ try {
+ return StopEstimatesResponseSchema.parse(data);
+ } catch (e) {
+ console.error("Zod parsing failed for estimates:", e);
+ console.log("Received data:", data);
+ throw e;
+ }
+};
diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts
index 0c55969..f68d413 100644
--- a/src/frontend/app/api/schema.ts
+++ b/src/frontend/app/api/schema.ts
@@ -64,10 +64,16 @@ export const ArrivalSchema = z.object({
shift: ShiftBadgeSchema.optional().nullable(),
shape: z.any().optional().nullable(),
currentPosition: PositionSchema.optional().nullable(),
- stopShapeIndex: z.number().optional().nullable(),
vehicleInformation: VehicleInformationSchema.optional().nullable(),
});
+export const ArrivalEstimateSchema = z.object({
+ tripId: z.string(),
+ patternId: z.string().optional().nullable(),
+ estimate: ArrivalDetailsSchema,
+ delay: DelayBadgeSchema.optional().nullable(),
+});
+
export const StopArrivalsResponseSchema = z.object({
stopCode: z.string(),
stopName: z.string(),
@@ -77,6 +83,10 @@ export const StopArrivalsResponseSchema = z.object({
usage: z.array(BusStopUsagePointSchema).optional().nullable(),
});
+export const StopEstimatesResponseSchema = z.object({
+ arrivals: z.array(ArrivalEstimateSchema),
+});
+
export type RouteInfo = z.infer<typeof RouteInfoSchema>;
export type HeadsignInfo = z.infer<typeof HeadsignInfoSchema>;
export type ArrivalPrecision = z.infer<typeof ArrivalPrecisionSchema>;
@@ -85,8 +95,10 @@ export type DelayBadge = z.infer<typeof DelayBadgeSchema>;
export type ShiftBadge = z.infer<typeof ShiftBadgeSchema>;
export type Position = z.infer<typeof PositionSchema>;
export type Arrival = z.infer<typeof ArrivalSchema>;
+export type ArrivalEstimate = z.infer<typeof ArrivalEstimateSchema>;
export type BusStopUsagePoint = z.infer<typeof BusStopUsagePointSchema>;
export type StopArrivalsResponse = z.infer<typeof StopArrivalsResponseSchema>;
+export type StopEstimatesResponse = z.infer<typeof StopEstimatesResponseSchema>;
// Transit Routes
export const RouteSchema = z.object({
diff --git a/src/frontend/app/components/stop/StopMapModal.tsx b/src/frontend/app/components/stop/StopMapModal.tsx
index 30ac63f..8d3c6f8 100644
--- a/src/frontend/app/components/stop/StopMapModal.tsx
+++ b/src/frontend/app/components/stop/StopMapModal.tsx
@@ -15,14 +15,13 @@ import "./StopMapModal.css";
export interface Position {
latitude: number;
longitude: number;
- orientationDegrees: number;
- shapeIndex?: number;
+ orientationDegrees?: number | null;
+ shapeIndex?: number | null | undefined;
}
export interface ConsolidatedCirculationForMap {
id: string;
currentPosition?: Position;
- stopShapeIndex?: number;
colour: string;
textColour: string;
shape?: any;
diff --git a/src/frontend/app/data/PlannerApi.ts b/src/frontend/app/data/PlannerApi.ts
index 43c8ae1..6f39f50 100644
--- a/src/frontend/app/data/PlannerApi.ts
+++ b/src/frontend/app/data/PlannerApi.ts
@@ -31,6 +31,8 @@ export interface Itinerary {
export interface Leg {
mode?: string;
feedId?: string;
+ routeId?: string;
+ tripId?: string;
routeName?: string;
routeShortName?: string;
routeLongName?: string;
diff --git a/src/frontend/app/hooks/useArrivals.ts b/src/frontend/app/hooks/useArrivals.ts
index e86a0bf..530ebc4 100644
--- a/src/frontend/app/hooks/useArrivals.ts
+++ b/src/frontend/app/hooks/useArrivals.ts
@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
-import { fetchArrivals } from "../api/arrivals";
+import { fetchArrivals, fetchEstimates } from "../api/arrivals";
export const useStopArrivals = (
stopId: string,
@@ -10,7 +10,22 @@ export const useStopArrivals = (
queryKey: ["arrivals", stopId, reduced],
queryFn: () => fetchArrivals(stopId, reduced),
enabled: !!stopId && enabled,
- refetchInterval: 15000, // Refresh every 15 seconds
- retry: false, // Disable retries to see errors immediately
+ refetchInterval: 15000,
+ retry: false,
+ });
+};
+
+export const useStopEstimates = (
+ stopId: string,
+ routeId: string,
+ viaStopId?: string,
+ enabled: boolean = true
+) => {
+ return useQuery({
+ queryKey: ["estimates", stopId, routeId, viaStopId],
+ queryFn: () => fetchEstimates(stopId, routeId, viaStopId),
+ enabled: !!stopId && !!routeId && enabled,
+ refetchInterval: 15000,
+ retry: false,
});
};
diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx
index b7ecaf9..4038ef7 100644
--- a/src/frontend/app/routes/planner.tsx
+++ b/src/frontend/app/routes/planner.tsx
@@ -6,7 +6,8 @@ import { useTranslation } from "react-i18next";
import { Layer, Source, type MapRef } from "react-map-gl/maplibre";
import { useLocation } from "react-router";
-import { type ConsolidatedCirculation } from "~/api/schema";
+import { fetchEstimates } from "~/api/arrivals";
+import { type StopEstimatesResponse } from "~/api/schema";
import { PlannerOverlay } from "~/components/PlannerOverlay";
import RouteIcon from "~/components/RouteIcon";
import { AppMap } from "~/components/shared/AppMap";
@@ -208,7 +209,7 @@ const ItineraryDetail = ({
const mapRef = useRef<MapRef>(null);
const { destination: userDestination } = usePlanner();
const [nextArrivals, setNextArrivals] = useState<
- Record<string, ConsolidatedCirculation[]>
+ Record<string, StopEstimatesResponse>
>({});
const routeGeoJson = {
@@ -324,27 +325,27 @@ const ItineraryDetail = ({
// Fetch next arrivals for bus legs
useEffect(() => {
- const fetchArrivals = async () => {
- const arrivalsByStop: Record<string, ConsolidatedCirculation[]> = {};
+ const fetchArrivalsForLegs = async () => {
+ const arrivalsByLeg: Record<string, StopEstimatesResponse> = {};
for (const leg of itinerary.legs) {
- if (leg.mode !== "WALK" && leg.from?.stopId) {
- const stopKey = leg.from.name || leg.from.stopId;
- if (!arrivalsByStop[stopKey]) {
+ if (
+ leg.mode !== "WALK" &&
+ leg.from?.stopId &&
+ leg.to?.stopId &&
+ leg.routeId
+ ) {
+ const legKey = `${leg.from.stopId}::${leg.to.stopId}`;
+ if (!arrivalsByLeg[legKey]) {
try {
- //TODO: Allow multiple stops one request
- const resp = await fetch(
- `/api/stops/arrivals?id=${encodeURIComponent(leg.from.stopId)}`,
- { headers: { Accept: "application/json" } }
+ arrivalsByLeg[legKey] = await fetchEstimates(
+ leg.from.stopId,
+ leg.routeId,
+ leg.to.stopId
);
-
- if (resp.ok) {
- arrivalsByStop[stopKey] =
- (await resp.json()) satisfies ConsolidatedCirculation[];
- }
} catch (err) {
console.warn(
- `Failed to fetch arrivals for ${leg.from.stopId}:`,
+ `Failed to fetch estimates for leg ${leg.from.stopId} -> ${leg.to.stopId}:`,
err
);
}
@@ -352,10 +353,10 @@ const ItineraryDetail = ({
}
}
- setNextArrivals(arrivalsByStop);
+ setNextArrivals(arrivalsByLeg);
};
- fetchArrivals();
+ fetchArrivalsForLegs();
}, [itinerary]);
return (
@@ -564,60 +565,45 @@ const ItineraryDetail = ({
</div>
{leg.mode !== "WALK" &&
leg.from?.stopId &&
- nextArrivals[leg.from.name || leg.from.stopId] && (
+ leg.to?.stopId &&
+ nextArrivals[`${leg.from.stopId}::${leg.to.stopId}`] && (
<div className="mt-2 text-xs text-gray-600 dark:text-gray-400">
<div className="font-semibold mb-1">
{t("planner.next_arrivals", "Next arrivals")}:
</div>
- {(() => {
- const currentLine =
- leg.routeShortName || leg.routeName;
- const previousLeg =
- idx > 0 ? itinerary.legs[idx - 1] : null;
- const previousLine =
- previousLeg?.mode !== "WALK"
- ? previousLeg?.routeShortName ||
- previousLeg?.routeName
- : null;
-
- const linesToShow = [currentLine];
- if (
- previousLine &&
- previousLeg?.to?.stopId === leg.from?.stopId
- ) {
- linesToShow.push(previousLine);
- }
-
- return nextArrivals[leg.from.stopId]
- ?.filter((circ) => linesToShow.includes(circ.line))
- .slice(0, 3)
- .map((circ, idx) => {
- const minutes =
- circ.realTime?.minutes ??
- circ.schedule?.minutes;
- if (minutes === undefined) return null;
- return (
- <div
- key={idx}
- className="flex items-center gap-2 py-0.5"
- >
- <span className="font-semibold">
- {circ.line}
- </span>
- <span className="text-gray-500 dark:text-gray-500">
- →
+ {nextArrivals[
+ `${leg.from.stopId}::${leg.to.stopId}`
+ ].arrivals
+ .slice(0, 3)
+ .map((arrival, i) => (
+ <div
+ key={`${arrival.tripId}-${i}`}
+ className="flex items-center gap-2 py-0.5"
+ >
+ <span className="font-semibold text-primary-600 dark:text-primary-400">
+ {formatDuration(arrival.estimate.minutes, t)}
+ </span>
+ {arrival.estimate.precision !== "scheduled" && (
+ <span className="text-green-600 dark:text-green-400">
+ 🟢
+ </span>
+ )}
+ {arrival.delay?.minutes !== undefined &&
+ arrival.delay.minutes !== 0 && (
+ <span
+ className={
+ arrival.delay.minutes > 0
+ ? "text-red-500"
+ : "text-green-500"
+ }
+ >
+ {arrival.delay.minutes > 0
+ ? `+${arrival.delay.minutes}′`
+ : `${arrival.delay.minutes}′`}
</span>
- <span className="flex-1 truncate">
- {circ.route}
- </span>
- <span className="font-semibold text-primary-600 dark:text-primary-400">
- {formatDuration(minutes, t)}
- {circ.realTime && " 🟢"}
- </span>
- </div>
- );
- });
- })()}
+ )}
+ </div>
+ ))}
</div>
)}
<div className="text-sm mt-1">
diff --git a/src/frontend/app/routes/routes-$id.tsx b/src/frontend/app/routes/routes-$id.tsx
index 2174244..bccaf56 100644
--- a/src/frontend/app/routes/routes-$id.tsx
+++ b/src/frontend/app/routes/routes-$id.tsx
@@ -19,7 +19,7 @@ import {
Source,
type MapRef,
} from "react-map-gl/maplibre";
-import { Link, useParams } from "react-router";
+import { Link, useLocation, useNavigate, useParams } from "react-router";
import { fetchRouteDetails } from "~/api/transit";
import { AppMap } from "~/components/shared/AppMap";
import {
@@ -28,7 +28,7 @@ import {
usePageTitle,
usePageTitleNode,
} from "~/contexts/PageTitleContext";
-import { useStopArrivals } from "~/hooks/useArrivals";
+import { useStopEstimates } from "~/hooks/useArrivals";
import { useFavorites } from "~/hooks/useFavorites";
import { formatHex } from "~/utils/colours";
import "../tailwind-full.css";
@@ -59,9 +59,9 @@ function FavoriteStar({ id }: { id?: string }) {
export default function RouteDetailsPage() {
const { id } = useParams();
const { t, i18n } = useTranslation();
- const [selectedPatternId, setSelectedPatternId] = useState<string | null>(
- null
- );
+ const navigate = useNavigate();
+ const location = useLocation();
+ const selectedPatternId = location.hash ? location.hash.slice(1) : null;
const [selectedStopId, setSelectedStopId] = useState<string | null>(null);
const [layoutMode, setLayoutMode] = useState<"balanced" | "map" | "list">(
"balanced"
@@ -103,34 +103,13 @@ export default function RouteDetailsPage() {
queryFn: () => fetchRouteDetails(id!, selectedDateKey),
enabled: !!id,
});
- const { data: selectedStopRealtime, isLoading: isRealtimeLoading } =
- useStopArrivals(
+ const { data: selectedStopEstimates, isLoading: isRealtimeLoading } =
+ useStopEstimates(
selectedStopId ?? "",
- true,
- Boolean(selectedStopId) && isTodaySelectedDate
+ id ?? "",
+ undefined,
+ Boolean(selectedStopId) && Boolean(id) && isTodaySelectedDate
);
- const filteredRealtimeArrivals = useMemo(() => {
- const arrivals = selectedStopRealtime?.arrivals ?? [];
- if (arrivals.length === 0) {
- return [];
- }
-
- const routeId = id?.trim();
- const routeShortName = route?.shortName?.trim().toLowerCase();
-
- return arrivals.filter((arrival) => {
- const arrivalGtfsId = arrival.route.gtfsId?.trim();
- if (routeId && arrivalGtfsId) {
- return arrivalGtfsId === routeId;
- }
-
- if (routeShortName) {
- return arrival.route.shortName.trim().toLowerCase() === routeShortName;
- }
-
- return true;
- });
- }, [selectedStopRealtime?.arrivals, id, route?.shortName]);
usePageTitle(
route?.shortName
@@ -589,7 +568,10 @@ export default function RouteDetailsPage() {
key={pattern.id}
type="button"
onClick={() => {
- setSelectedPatternId(pattern.id);
+ navigate(
+ { hash: "#" + pattern.id },
+ { replace: true }
+ );
setSelectedStopId(null);
setIsPatternPickerOpen(false);
}}
@@ -748,33 +730,31 @@ export default function RouteDetailsPage() {
{selectedStopId === stop.id &&
(departuresByStop.get(stop.id)?.length ?? 0) > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
- {(
- departuresByStop
- .get(stop.id)
- ?.filter((item) =>
- isTodaySelectedDate
- ? item.departure >=
- nowSeconds - ONE_HOUR_SECONDS
- : true
- ) ?? []
- ).map((item, i) => (
- <span
- key={`${item.patternId}-${item.departure}-${i}`}
- className={`text-[11px] px-2 py-0.5 rounded ${
- item.patternId === selectedPattern?.id
- ? "bg-gray-100 dark:bg-gray-900"
- : "bg-gray-50 dark:bg-gray-900 text-gray-400 font-light"
- }`}
- >
- {Math.floor(item.departure / 3600)
- .toString()
- .padStart(2, "0")}
- :
- {Math.floor((item.departure % 3600) / 60)
- .toString()
- .padStart(2, "0")}
- </span>
- ))}
+ {(departuresByStop.get(stop.id) ?? []).map(
+ (item, i) => {
+ const isPast =
+ isTodaySelectedDate &&
+ item.departure < nowSeconds;
+ return (
+ <span
+ key={`${item.patternId}-${item.departure}-${i}`}
+ className={`text-[11px] px-2 py-0.5 rounded ${
+ item.patternId === selectedPattern?.id
+ ? "bg-gray-100 dark:bg-gray-900"
+ : "bg-gray-50 dark:bg-gray-900 text-gray-400 font-light"
+ } ${isPast ? "line-through opacity-50" : ""}`}
+ >
+ {Math.floor(item.departure / 3600)
+ .toString()
+ .padStart(2, "0")}
+ :
+ {Math.floor((item.departure % 3600) / 60)
+ .toString()
+ .padStart(2, "0")}
+ </span>
+ );
+ }
+ )}
</div>
)}
@@ -787,7 +767,8 @@ export default function RouteDetailsPage() {
<div className="text-[11px] text-muted">
{t("routes.loading_realtime", "Cargando...")}
</div>
- ) : filteredRealtimeArrivals.length === 0 ? (
+ ) : (selectedStopEstimates?.arrivals.length ?? 0) ===
+ 0 ? (
<div className="text-[11px] text-muted">
{t(
"routes.realtime_no_route_estimates",
@@ -796,37 +777,67 @@ export default function RouteDetailsPage() {
</div>
) : (
<>
- <div className="flex items-center justify-between gap-2 rounded-lg border border-green-500/30 bg-green-500/10 px-2.5 py-2">
- <span className="text-[11px] font-semibold uppercase tracking-wide text-green-700 dark:text-green-300">
- {t("routes.next_arrival", "Próximo")}
- </span>
- <span className="inline-flex min-w-16 items-center justify-center rounded-xl bg-green-600 px-3 py-1.5 text-base font-bold leading-none text-white">
- {filteredRealtimeArrivals[0].estimate.minutes}′
- {filteredRealtimeArrivals[0].delay?.minutes
- ? formatDelayMinutes(
- filteredRealtimeArrivals[0].delay.minutes
- )
- : ""}
- </span>
- </div>
+ {(() => {
+ const firstArrival =
+ selectedStopEstimates!.arrivals[0];
+ const isFirstSelectedPattern =
+ firstArrival.patternId === selectedPattern?.id;
+ return (
+ <div
+ className={`flex items-center justify-between gap-2 rounded-lg border px-2.5 py-2 ${isFirstSelectedPattern ? "border-green-500/30 bg-green-500/10" : "border-emerald-500/20 bg-emerald-500/5 opacity-50"}`}
+ >
+ <span
+ className={`text-[11px] font-semibold uppercase tracking-wide ${isFirstSelectedPattern ? "text-green-700 dark:text-green-300" : "text-emerald-700 dark:text-emerald-400"}`}
+ >
+ {t("routes.next_arrival", "Próximo")}
+ </span>
+ <span
+ className={`inline-flex min-w-16 items-center justify-center rounded-xl px-3 py-1.5 text-base font-bold leading-none text-white ${isFirstSelectedPattern ? "bg-green-600" : "bg-emerald-600"}`}
+ >
+ {firstArrival.estimate.minutes}′
+ {firstArrival.delay?.minutes
+ ? formatDelayMinutes(
+ firstArrival.delay.minutes
+ )
+ : ""}
+ </span>
+ </div>
+ );
+ })()}
- {filteredRealtimeArrivals.length > 1 && (
+ {selectedStopEstimates!.arrivals.length > 1 && (
<div className="mt-2 flex flex-wrap justify-end gap-1">
- {filteredRealtimeArrivals
+ {selectedStopEstimates!.arrivals
.slice(1)
- .map((arrival, i) => (
- <span
- key={`${arrival.tripId}-${i}`}
- className="text-[11px] px-2 py-0.5 bg-primary/10 text-primary rounded"
- >
- {arrival.estimate.minutes}′
- {arrival.delay?.minutes
- ? formatDelayMinutes(
- arrival.delay.minutes
- )
- : ""}
- </span>
- ))}
+ .map((arrival, i) => {
+ const isSelectedPattern =
+ arrival.patternId === selectedPattern?.id;
+ return (
+ <span
+ key={`${arrival.tripId}-${i}`}
+ className={`text-[11px] px-2 py-0.5 rounded ${
+ isSelectedPattern
+ ? "bg-gray-100 dark:bg-gray-900"
+ : "bg-gray-50 dark:bg-gray-900 text-gray-400 font-light"
+ }`}
+ title={
+ isSelectedPattern
+ ? undefined
+ : t(
+ "routes.other_pattern",
+ "Otro trayecto"
+ )
+ }
+ >
+ {arrival.estimate.minutes}′
+ {arrival.delay?.minutes
+ ? formatDelayMinutes(
+ arrival.delay.minutes
+ )
+ : ""}
+ </span>
+ );
+ })}
</div>
)}
</>
diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx
index a61e925..4b32040 100644
--- a/src/frontend/app/routes/stops-$id.tsx
+++ b/src/frontend/app/routes/stops-$id.tsx
@@ -274,7 +274,6 @@ export default function Estimates() {
circulations={(data?.arrivals ?? []).map((a) => ({
id: getArrivalId(a),
currentPosition: a.currentPosition ?? undefined,
- stopShapeIndex: a.stopShapeIndex ?? undefined,
colour: formatHex(a.route.colour),
textColour: formatHex(a.route.textColour),
shape: a.shape,