diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs | 29 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Controllers/TileController.cs | 98 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs | 20 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/GraphClient/App/StopTile.cs | 20 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Types/Arrivals/Arrival.cs | 9 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/appsettings.Development.json | 8 | ||||
| -rw-r--r-- | src/frontend/app/api/schema.ts | 8 | ||||
| -rw-r--r-- | src/frontend/app/components/arrivals/ArrivalCard.css (renamed from src/frontend/app/components/Stops/ArrivalCard.css) | 0 | ||||
| -rw-r--r-- | src/frontend/app/components/arrivals/ArrivalCard.tsx (renamed from src/frontend/app/components/Stops/ArrivalCard.tsx) | 10 | ||||
| -rw-r--r-- | src/frontend/app/components/arrivals/ArrivalList.tsx (renamed from src/frontend/app/components/Stops/ArrivalList.tsx) | 0 | ||||
| -rw-r--r-- | src/frontend/app/components/map/StopSummarySheet.tsx | 2 |
11 files changed, 138 insertions, 66 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs index 5dee48d..7158137 100644 --- a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs +++ b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs @@ -1,7 +1,6 @@ using System.Net; using Costasdev.Busurbano.Backend.GraphClient; using Costasdev.Busurbano.Backend.GraphClient.App; -using Costasdev.Busurbano.Backend.Types; using Costasdev.Busurbano.Backend.Types.Arrivals; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; @@ -37,7 +36,14 @@ public partial class ArrivalsController : ControllerBase var nowLocal = TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz); var todayLocal = nowLocal.Date; - var requestContent = ArrivalsAtStopContent.Query(new(id, reduced ? 4 : 10)); + var requestContent = ArrivalsAtStopContent.Query( + new ArrivalsAtStopContent.Args( + id, + reduced ? 4 : 10, + ShouldFetchPastArrivals(id) + ) + ); + var request = new HttpRequestMessage(HttpMethod.Post, "http://100.67.54.115:3957/otp/gtfs/v1"); request.Content = JsonContent.Create(new GraphClientRequest { @@ -61,13 +67,20 @@ public partial class ArrivalsController : ControllerBase var minutesToArrive = (int)(departureTime - nowLocal).TotalMinutes; //var isRunning = departureTime < nowLocal; + // TODO: Handle this properly, since many times it's "tomorrow" but not handled properly + if (minutesToArrive < ArrivalsAtStopContent.PastArrivalMinutesIncluded) + { + continue; + } + Arrival arrival = new() { + TripId = item.Trip.GtfsId, Route = new RouteInfo { ShortName = item.Trip.RouteShortName, - Colour = item.Trip.Route.Color, - TextColour = item.Trip.Route.TextColor + Colour = item.Trip.Route.Color ?? "FFFFFF", + TextColour = item.Trip.Route.TextColor ?? "000000" }, Headsign = new HeadsignInfo { @@ -76,7 +89,7 @@ public partial class ArrivalsController : ControllerBase Estimate = new ArrivalDetails { Minutes = minutesToArrive, - Precission = departureTime < nowLocal ? ArrivalPrecission.Past : ArrivalPrecission.Scheduled + Precision = departureTime < nowLocal ? ArrivalPrecision.Past : ArrivalPrecision.Scheduled } }; @@ -91,6 +104,12 @@ public partial class ArrivalsController : ControllerBase }); } + private static bool ShouldFetchPastArrivals(string id) + { + string feedId = id.Split(':', 2)[0]; + return feedId == "xunta"; + } + [LoggerMessage(LogLevel.Error, "Error fetching stop data, received {statusCode} {responseBody}")] partial void LogErrorFetchingStopData(HttpStatusCode statusCode, string responseBody); } diff --git a/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs b/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs index fad18e7..6354d67 100644 --- a/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs +++ b/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs @@ -46,9 +46,9 @@ public class TileController : ControllerBase [HttpGet("stops/{z:int}/{x:int}/{y:int}")] public async Task<IActionResult> Stops(int z, int x, int y) { - if (z < 9 || z > 16) + if (z is < 9 or > 20) { - return BadRequest("Zoom level out of range (9-16)"); + return BadRequest("Zoom level out of range (9-20)"); } var cacheHit = _cache.TryGetValue($"stops-tile-{z}-{x}-{y}", out byte[]? cachedTile); @@ -96,10 +96,11 @@ public class TileController : ControllerBase responseBody.Data?.StopsByBbox?.ForEach(stop => { var idParts = stop.GtfsId.Split(':', 2); + string feedId = idParts[0]; string codeWithinFeed = stop.Code ?? string.Empty; // TODO: Refactor this, maybe do it client-side or smth - if (idParts[0] == "vitrasa") + if (feedId == "vitrasa") { var digits = new string(codeWithinFeed.Where(char.IsDigit).ToArray()); if (int.TryParse(digits, out int code)) @@ -108,12 +109,13 @@ public class TileController : ControllerBase } } - if (HiddenStops.Contains($"{idParts[0]}:{codeWithinFeed}")) + if (HiddenStops.Contains($"{feedId}:{codeWithinFeed}")) { return; } - var fallbackColours = GetFallbackColourForFeed(idParts[0]); + var (Color, TextColor) = GetFallbackColourForFeed(idParts[0]); + var distinctRoutes = GetDistinctRoutes(feedId, stop.Routes ?? []); Feature feature = new() { @@ -129,36 +131,34 @@ public class TileController : ControllerBase // The name of the stop { "name", stop.Name }, // Routes - { "routes", JsonSerializer.Serialize(stop.Routes? - .DistinctBy(r => r.ShortName) - .OrderBy( - r => r.ShortName, - Comparer<string?>.Create(SortingHelper.SortRouteShortNames) - ).Select(r => { - var colour = r.Color ?? fallbackColours.Color; - string textColour; + { "routes", JsonSerializer + .Serialize( + distinctRoutes.Select(r => { + var colour = r.Color ?? Color; + string textColour; - if (r.Color is null) // None is present, use fallback - { - textColour = fallbackColours.TextColor; - } - else if (r.TextColor is null || r.TextColor.EndsWith("000000")) - { - // Text colour not provided, or default-black; check the better contrasting - textColour = ContrastHelper.GetBestTextColour(colour); - } - else - { - // Use provided text colour - textColour = r.TextColor; - } + if (r.Color is null) // None is present, use fallback + { + textColour = TextColor; + } + else if (r.TextColor is null || r.TextColor.EndsWith("000000")) + { + // Text colour not provided, or default-black; check the better contrasting + textColour = ContrastHelper.GetBestTextColour(colour); + } + else + { + // Use provided text colour + textColour = r.TextColor; + } - return new { - shortName = r.ShortName, - colour, - textColour - }; - })) } + return new { + shortName = r.ShortName, + colour, + textColour + }; + })) + } } }; @@ -176,6 +176,37 @@ public class TileController : ControllerBase return File(ms.ToArray(), "application/x-protobuf"); } + private static List<StopTileResponse.Route> GetDistinctRoutes(string feedId, List<StopTileResponse.Route> routes) + { + List<StopTileResponse.Route> distinctRoutes = []; + HashSet<string> seen = new(); + + foreach (var route in routes) + { + var seenId = route.ShortName; + if (feedId == "xunta") + { + // For Xunta routes we take only the contract number (XG123, for example) + seenId = seenId.Substring(0, 5); + + route.ShortName = seenId; + } + + if (seen.Contains(seenId)) + { + continue; + } + + seen.Add(seenId); + distinctRoutes.Add(route); + } + + return [.. distinctRoutes.OrderBy( + r => r.ShortName, + Comparer<string?>.Create(SortingHelper.SortRouteShortNames) + )]; + } + private static (string Color, string TextColor) GetFallbackColourForFeed(string feed) { return feed switch @@ -190,5 +221,4 @@ public class TileController : ControllerBase }; } - } diff --git a/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs b/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs index 53c1165..2c34784 100644 --- a/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs +++ b/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs @@ -5,16 +5,26 @@ namespace Costasdev.Busurbano.Backend.GraphClient.App; public class ArrivalsAtStopContent : IGraphRequest<ArrivalsAtStopContent.Args> { - public record Args(string Id, int DepartureCount); + public const int PastArrivalMinutesIncluded = -15; + + public record Args(string Id, int DepartureCount, bool PastArrivals); public static string Query(Args args) { + var startTime = DateTimeOffset.Now; + if (args.PastArrivals) + { + startTime = DateTimeOffset.Now.AddMinutes(PastArrivalMinutesIncluded); + } + + var startTimeUnix = startTime.ToUnixTimeSeconds(); + return string.Create(CultureInfo.InvariantCulture, $@" query Query {{ stop(id:""{args.Id}"") {{ code name - arrivals: stoptimesWithoutPatterns(numberOfDepartures:{args.DepartureCount}) {{ + arrivals: stoptimesWithoutPatterns(numberOfDepartures:{args.DepartureCount}, startTime: {startTimeUnix}) {{ headsign scheduledDeparture pickupType @@ -40,7 +50,7 @@ public class ArrivalsAtStopContent : IGraphRequest<ArrivalsAtStopContent.Args> public class ArrivalsAtStopResponse : AbstractGraphResponse { - [JsonPropertyName("stop")] public StopItem Stop { get; set; } + [JsonPropertyName("stop")] public required StopItem Stop { get; set; } public class StopItem { @@ -87,9 +97,9 @@ public class ArrivalsAtStopResponse : AbstractGraphResponse public class RouteDetails { - [JsonPropertyName("color")] public required string Color { get; set; } + [JsonPropertyName("color")] public string? Color { get; set; } - [JsonPropertyName("textColor")] public required string TextColor { get; set; } + [JsonPropertyName("textColor")] public string? TextColor { get; set; } } public class PickupType diff --git a/src/Costasdev.Busurbano.Backend/GraphClient/App/StopTile.cs b/src/Costasdev.Busurbano.Backend/GraphClient/App/StopTile.cs index 8a271f2..802de9a 100644 --- a/src/Costasdev.Busurbano.Backend/GraphClient/App/StopTile.cs +++ b/src/Costasdev.Busurbano.Backend/GraphClient/App/StopTile.cs @@ -42,35 +42,35 @@ public class StopTileResponse : AbstractGraphResponse public record Stop { [JsonPropertyName("gtfsId")] - public required string GtfsId { get; init; } + public required string GtfsId { get; set; } [JsonPropertyName("code")] - public string? Code { get; init; } + public string? Code { get; set; } [JsonPropertyName("name")] - public required string Name { get; init; } + public required string Name { get; set; } [JsonPropertyName("lat")] - public required double Lat { get; init; } + public required double Lat { get; set; } [JsonPropertyName("lon")] - public required double Lon { get; init; } + public required double Lon { get; set; } [JsonPropertyName("routes")] - public List<Route>? Routes { get; init; } + public List<Route>? Routes { get; set; } } public record Route { [JsonPropertyName("gtfsId")] - public required string GtfsId { get; init; } + public required string GtfsId { get; set; } [JsonPropertyName("shortName")] - public required string ShortName { get; init; } + public required string ShortName { get; set; } [JsonPropertyName("color")] - public string? Color { get; init; } + public string? Color { get; set; } [JsonPropertyName("textColor")] - public string? TextColor { get; init; } + public string? TextColor { get; set; } } } diff --git a/src/Costasdev.Busurbano.Backend/Types/Arrivals/Arrival.cs b/src/Costasdev.Busurbano.Backend/Types/Arrivals/Arrival.cs index c813ccf..516a1c5 100644 --- a/src/Costasdev.Busurbano.Backend/Types/Arrivals/Arrival.cs +++ b/src/Costasdev.Busurbano.Backend/Types/Arrivals/Arrival.cs @@ -4,6 +4,9 @@ namespace Costasdev.Busurbano.Backend.Types.Arrivals; public class Arrival { + [JsonPropertyName("tripId")] + public required string TripId { get; set; } + [JsonPropertyName("route")] public required RouteInfo Route { get; set; } @@ -49,12 +52,12 @@ public class ArrivalDetails [JsonPropertyName("minutes")] public required int Minutes { get; set; } - [JsonPropertyName("precission")] - public ArrivalPrecission Precission { get; set; } = ArrivalPrecission.Scheduled; + [JsonPropertyName("precision")] + public ArrivalPrecision Precision { get; set; } = ArrivalPrecision.Scheduled; } [JsonConverter(typeof(JsonStringEnumConverter))] -public enum ArrivalPrecission +public enum ArrivalPrecision { [JsonStringEnumMemberName("confident")] Confident = 0, diff --git a/src/Costasdev.Busurbano.Backend/appsettings.Development.json b/src/Costasdev.Busurbano.Backend/appsettings.Development.json new file mode 100644 index 0000000..2b38630 --- /dev/null +++ b/src/Costasdev.Busurbano.Backend/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Costasdev.Busurbano.Backend": "Debug" + } + }, + "AllowedHosts": "*" +} diff --git a/src/frontend/app/api/schema.ts b/src/frontend/app/api/schema.ts index 60e2d97..bb1e96c 100644 --- a/src/frontend/app/api/schema.ts +++ b/src/frontend/app/api/schema.ts @@ -12,7 +12,7 @@ export const HeadsignInfoSchema = z.object({ marquee: z.string().optional().nullable(), }); -export const ArrivalPrecissionSchema = z.enum([ +export const ArrivalPrecisionSchema = z.enum([ "confident", "unsure", "scheduled", @@ -20,8 +20,8 @@ export const ArrivalPrecissionSchema = z.enum([ ]); export const ArrivalDetailsSchema = z.object({ - minutes: z.number(), - precission: ArrivalPrecissionSchema, + minutes: z.number().int(), + precision: ArrivalPrecisionSchema, }); export const DelayBadgeSchema = z.object({ @@ -49,7 +49,7 @@ export const StopArrivalsResponseSchema = z.object({ export type RouteInfo = z.infer<typeof RouteInfoSchema>; export type HeadsignInfo = z.infer<typeof HeadsignInfoSchema>; -export type ArrivalPrecission = z.infer<typeof ArrivalPrecissionSchema>; +export type ArrivalPrecision = z.infer<typeof ArrivalPrecisionSchema>; export type ArrivalDetails = z.infer<typeof ArrivalDetailsSchema>; export type DelayBadge = z.infer<typeof DelayBadgeSchema>; export type ShiftBadge = z.infer<typeof ShiftBadgeSchema>; diff --git a/src/frontend/app/components/Stops/ArrivalCard.css b/src/frontend/app/components/arrivals/ArrivalCard.css index 5835352..5835352 100644 --- a/src/frontend/app/components/Stops/ArrivalCard.css +++ b/src/frontend/app/components/arrivals/ArrivalCard.css diff --git a/src/frontend/app/components/Stops/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx index 96d0af0..de4fcc7 100644 --- a/src/frontend/app/components/Stops/ArrivalCard.tsx +++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx @@ -16,11 +16,11 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({ const { t } = useTranslation(); const { route, headsign, estimate } = arrival; - const etaValue = Math.max(0, Math.round(estimate.minutes)).toString(); + const etaValue = estimate.minutes.toString(); const etaUnit = t("estimates.minutes", "min"); const timeClass = useMemo(() => { - switch (estimate.precission) { + switch (estimate.precision) { case "confident": return "time-running"; case "unsure": @@ -30,7 +30,7 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({ default: return "time-scheduled"; } - }, [estimate.precission]); + }, [estimate.precision]); return ( <div @@ -50,7 +50,9 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({ /> </div> <div className="flex-1 min-w-0 flex flex-col gap-1"> - <strong className="text-base text-(--text-color) overflow-hidden text-ellipsis line-clamp-2 leading-tight"> + <strong + className={`text-base overflow-hidden text-ellipsis line-clamp-2 leading-tight ${estimate.precision == "past" ? "line-through" : ""}`} + > {headsign.destination} </strong> </div> diff --git a/src/frontend/app/components/Stops/ArrivalList.tsx b/src/frontend/app/components/arrivals/ArrivalList.tsx index a1210d5..a1210d5 100644 --- a/src/frontend/app/components/Stops/ArrivalList.tsx +++ b/src/frontend/app/components/arrivals/ArrivalList.tsx diff --git a/src/frontend/app/components/map/StopSummarySheet.tsx b/src/frontend/app/components/map/StopSummarySheet.tsx index 16a9cbe..e318bee 100644 --- a/src/frontend/app/components/map/StopSummarySheet.tsx +++ b/src/frontend/app/components/map/StopSummarySheet.tsx @@ -3,7 +3,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Sheet } from "react-modal-sheet"; import { Link } from "react-router"; -import { ArrivalList } from "~/components/Stops/ArrivalList"; +import { ArrivalList } from "~/components/arrivals/ArrivalList"; import { useStopArrivals } from "../../hooks/useArrivals"; import { ErrorDisplay } from "../ErrorDisplay"; import LineIcon from "../LineIcon"; |
