diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs | 52 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Controllers/TileController.cs (renamed from src/Costasdev.Busurbano.Backend/Controllers/StopsTile.cs) | 72 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Costasdev.Busurbano.Backend.csproj | 2 | ||||
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs | 82 | ||||
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 17 |
5 files changed, 182 insertions, 43 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs new file mode 100644 index 0000000..eb81784 --- /dev/null +++ b/src/Costasdev.Busurbano.Backend/Controllers/ArrivalsController.cs @@ -0,0 +1,52 @@ +using Costasdev.Busurbano.Backend.GraphClient; +using Costasdev.Busurbano.Backend.GraphClient.App; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; + +namespace Costasdev.Busurbano.Backend.Controllers; + +[ApiController] +[Route("api")] +public class ArrivalsController : ControllerBase +{ + private readonly ILogger<ArrivalsController> _logger; + private readonly IMemoryCache _cache; + private readonly HttpClient _httpClient; + + public ArrivalsController( + ILogger<ArrivalsController> logger, + IMemoryCache cache, + HttpClient httpClient + ) + { + _logger = logger; + _cache = cache; + _httpClient = httpClient; + } + + [HttpGet("arrivals")] + public async Task<IActionResult> GetArrivals(string id) + { + var requestContent = ArrivalsAtStopContent.Query(id); + var request = new HttpRequestMessage(HttpMethod.Post, "http://100.67.54.115:3957/otp/gtfs/v1"); + 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 }) + { + _logger.LogError( + "Error fetching stop data, received {StatusCode} {ResponseBody}", + response.StatusCode, + await response.Content.ReadAsStringAsync() + ); + return StatusCode(500, "Error fetching stop data"); + } + + return Ok(responseBody.Data?.Stop); + } +} diff --git a/src/Costasdev.Busurbano.Backend/Controllers/StopsTile.cs b/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs index 56f836e..fad18e7 100644 --- a/src/Costasdev.Busurbano.Backend/Controllers/StopsTile.cs +++ b/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs @@ -1,15 +1,18 @@ using Costasdev.Busurbano.Backend.GraphClient; using Costasdev.Busurbano.Backend.GraphClient.App; + +using NetTopologySuite.Features; using NetTopologySuite.IO.VectorTiles; using NetTopologySuite.IO.VectorTiles.Mapbox; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; -using NetTopologySuite.Features; using System.Text.Json; using Costasdev.Busurbano.Backend.Helpers; +namespace Costasdev.Busurbano.Backend.Controllers; + [ApiController] [Route("api/tiles")] public class TileController : ControllerBase @@ -29,36 +32,25 @@ public class TileController : ControllerBase _httpClient = httpClient; } - /* - vitrasa:20223: # Castrelos (Pavillón) - Final U1 - hide: true -vitrasa:20146: # García Barbón 7 - final líneas A y 18A - hide: true -vitrasa:20220: # (Samil) COIA-SAMIL - Final L15A - hide: true -vitrasa:20001: # (Samil) Samil por Beiramar - Final L15B - hide: true -vitrasa:20002: # (Samil) Samil por Torrecedeira - Final L15C - hide: true -vitrasa:20144: # (Samil) Samil por Coia - Final C3D+C3i - hide: true -vitrasa:20145: # (Samil) Samil por Bouzas - Final C3D+C3i - hide: true - */ private static readonly string[] HiddenStops = [ - "vitrasa:20223", - "vitrasa:20146", - "vitrasa:20220", - "vitrasa:20001", - "vitrasa:20002", - "vitrasa:20144", - "vitrasa:20145" + "vitrasa:20223", // Castrelos (Pavillón - U1) + "vitrasa:20146", // García Barbón, 7 (A, 18A) + "vitrasa:20220", // COIA-SAMIL (15) + "vitrasa:20001", // Samil por Beiramar (15B) + "vitrasa:20002", // Samil por Torrecedeira (15C) + "vitrasa:20144", // Samil por Coia (C3d, C3i) + "vitrasa:20145" // Samil por Bouzs (C3d, C3i) ]; - [HttpGet("stops/{z}/{x}/{y}")] - public async Task<IActionResult> GetTrafficTile(int z, int x, int y) + [HttpGet("stops/{z:int}/{x:int}/{y:int}")] + public async Task<IActionResult> Stops(int z, int x, int y) { + if (z < 9 || z > 16) + { + return BadRequest("Zoom level out of range (9-16)"); + } + var cacheHit = _cache.TryGetValue($"stops-tile-{z}-{x}-{y}", out byte[]? cachedTile); if (cacheHit && cachedTile != null) { @@ -67,15 +59,15 @@ vitrasa:20145: # (Samil) Samil por Bouzas - Final C3D+C3i } // Calculate bounding box in EPSG:4326 - double n = Math.Pow(2, z); - double lonMin = x / n * 360.0 - 180.0; - double lonMax = (x + 1) / n * 360.0 - 180.0; + var n = Math.Pow(2, z); + var lonMin = x / n * 360.0 - 180.0; + var lonMax = (x + 1) / n * 360.0 - 180.0; - double latMaxRad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * y / n))); - double latMax = latMaxRad * 180.0 / Math.PI; + var latMaxRad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * y / n))); + var latMax = latMaxRad * 180.0 / Math.PI; - double latMinRad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * (y + 1) / n))); - double latMin = latMinRad * 180.0 / Math.PI; + var latMinRad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * (y + 1) / n))); + var latMin = latMinRad * 180.0 / Math.PI; var requestContent = StopTileRequestContent.Query(new StopTileRequestContent.Bbox(lonMin, latMin, lonMax, latMax)); var request = new HttpRequestMessage(HttpMethod.Post, "http://100.67.54.115:3957/otp/gtfs/v1"); @@ -87,11 +79,13 @@ vitrasa:20145: # (Samil) Samil por Bouzas - Final C3D+C3i var response = await _httpClient.SendAsync(request); var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<StopTileResponse>>(); - if (responseBody == null || !responseBody.IsSuccess) + if (responseBody is not { IsSuccess: true }) { - _logger.LogError("Error fetching stop data: {StatusCode}", response.StatusCode); - _logger.LogError("Sent request: {RequestContent}", await request.Content.ReadAsStringAsync()); - _logger.LogError("Response body: {ResponseBody}", await response.Content.ReadAsStringAsync()); + _logger.LogError( + "Error fetching stop data, received {StatusCode} {ResponseBody}", + response.StatusCode, + await response.Content.ReadAsStringAsync() + ); return StatusCode(500, "Error fetching stop data"); } @@ -135,7 +129,9 @@ vitrasa:20145: # (Samil) Samil por Bouzas - Final C3D+C3i // The name of the stop { "name", stop.Name }, // Routes - { "routes", JsonSerializer.Serialize(stop.Routes?.OrderBy( + { "routes", JsonSerializer.Serialize(stop.Routes? + .DistinctBy(r => r.ShortName) + .OrderBy( r => r.ShortName, Comparer<string?>.Create(SortingHelper.SortRouteShortNames) ).Select(r => { diff --git a/src/Costasdev.Busurbano.Backend/Costasdev.Busurbano.Backend.csproj b/src/Costasdev.Busurbano.Backend/Costasdev.Busurbano.Backend.csproj index 4b556da..3bff631 100644 --- a/src/Costasdev.Busurbano.Backend/Costasdev.Busurbano.Backend.csproj +++ b/src/Costasdev.Busurbano.Backend/Costasdev.Busurbano.Backend.csproj @@ -1,6 +1,6 @@ <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <RootNamespace>Costasdev.Busurbano.Backend</RootNamespace> diff --git a/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs b/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs new file mode 100644 index 0000000..dfecdd6 --- /dev/null +++ b/src/Costasdev.Busurbano.Backend/GraphClient/App/ArrivalsAtStop.cs @@ -0,0 +1,82 @@ +using System.Globalization; +using System.Text.Json.Serialization; + +namespace Costasdev.Busurbano.Backend.GraphClient.App; + +public class ArrivalsAtStopContent : IGraphRequest<string> +{ + public static string Query(string id) + { + return string.Create(CultureInfo.InvariantCulture, $@" + query Query {{ + stop(id:""{id}"") {{ + code + name + arrivals: stoptimesWithoutPatterns(numberOfDepartures:10) {{ + trip {{ + gtfsId + routeShortName + route {{ + color + textColor + }} + }} + headsign + scheduledDeparture + }} + }} + }} + "); + } +} + +public class ArrivalsAtStopResponse : AbstractGraphResponse +{ + [JsonPropertyName("stop")] + public StopItem Stop { get; set; } + + public class StopItem + { + [JsonPropertyName("code")] + public required string Code { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("arrivals")] + public List<Arrival> Arrivals { get; set; } = []; + } + + public class Arrival + { + [JsonPropertyName("headsign")] + public required string Headsign { get; set; } + + [JsonPropertyName("scheduledDeparture")] + public int ScheduledDepartureSeconds { get; set; } + + [JsonPropertyName("trip")] + public required TripDetails Trip { get; set; } + } + + public class TripDetails + { + [JsonPropertyName("gtfsId")] + public required string GtfsId { get; set; } + + [JsonPropertyName("routeShortName")] + public required string RouteShortName { get; set; } + + [JsonPropertyName("route")] + public required RouteDetails Route { get; set; } + } + + public class RouteDetails + { + [JsonPropertyName("color")] + public required string Color { get; set; } + + [JsonPropertyName("textColor")] + public required string TextColor { get; set; } + } +} diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index db9de59..279f096 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -122,7 +122,12 @@ export default function StopMap() { Array.isArray(center) ? center[1] : center.lng; const handlePointClick = (feature: any) => { - const props: any = feature.properties; + const props: { + id: string; + code: string; + name: string; + routes: string; + } = feature.properties; // TODO: Move ID to constant, improve type checking if (!props || feature.layer.id !== "stops") { console.warn("Invalid feature properties:", props); @@ -130,14 +135,18 @@ export default function StopMap() { } const stopId = props.id; - - console.debug("Stop clicked:", stopId, props); + const routes: { + shortName: string; + colour: string; + textColour: string; + }[] = JSON.parse(props.routes || "[]"); setSelectedStop({ stopId: props.id, stopCode: props.code, name: props.name || "Unknown Stop", - lines: JSON.parse(props.routes || "[]").map((route) => { + lines: routes.map((route) => { + console.log(route); return { line: route.shortName, colour: route.colour, |
