diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-29 00:41:52 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-29 00:41:52 +0100 |
| commit | a304c24b32c0327436bbd8c2853e60668e161b42 (patch) | |
| tree | 08f65c05daca134cf4d2e4f779bd15d98fd66370 /src/Costasdev.Busurbano.Backend/Controllers/TileController.cs | |
| parent | 120a3c6bddd0fb8d9fa05df4763596956554c025 (diff) | |
Rename a lot of stuff, add Santiago real time
Diffstat (limited to 'src/Costasdev.Busurbano.Backend/Controllers/TileController.cs')
| -rw-r--r-- | src/Costasdev.Busurbano.Backend/Controllers/TileController.cs | 217 |
1 files changed, 0 insertions, 217 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs b/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs deleted file mode 100644 index 0e550a3..0000000 --- a/src/Costasdev.Busurbano.Backend/Controllers/TileController.cs +++ /dev/null @@ -1,217 +0,0 @@ -using NetTopologySuite.Features; -using NetTopologySuite.IO.VectorTiles; -using NetTopologySuite.IO.VectorTiles.Mapbox; - -using Microsoft.AspNetCore.Mvc; - -using Microsoft.Extensions.Caching.Memory; -using System.Text.Json; -using Costasdev.Busurbano.Backend.Helpers; -using Costasdev.Busurbano.Backend.Services; -using Costasdev.Busurbano.Backend.Configuration; -using Costasdev.Busurbano.Sources.OpenTripPlannerGql; -using Costasdev.Busurbano.Sources.OpenTripPlannerGql.Queries; -using Microsoft.Extensions.Options; - -namespace Costasdev.Busurbano.Backend.Controllers; - -[ApiController] -[Route("api/tiles")] -public class TileController : ControllerBase -{ - private readonly ILogger<TileController> _logger; - private readonly IMemoryCache _cache; - private readonly HttpClient _httpClient; - private readonly FeedService _feedService; - private readonly AppConfiguration _config; - - public TileController( - ILogger<TileController> logger, - IMemoryCache cache, - HttpClient httpClient, - FeedService feedService, - IOptions<AppConfiguration> configOptions - ) - { - _logger = logger; - _cache = cache; - _httpClient = httpClient; - _feedService = feedService; - _config = configOptions.Value; - } - - [HttpGet("stops/{z:int}/{x:int}/{y:int}")] - public async Task<IActionResult> Stops(int z, int x, int y) - { - if (z is < 9 or > 20) - { - return BadRequest("Zoom level out of range (9-20)"); - } - - var cacheHit = _cache.TryGetValue($"stops-tile-{z}-{x}-{y}", out byte[]? cachedTile); - if (cacheHit && cachedTile != null) - { - Response.Headers.Append("X-Cache-Hit", "true"); - return File(cachedTile, "application/x-protobuf"); - } - - // Calculate bounding box in EPSG:4326 - var n = Math.Pow(2, z); - var lonMin = x / n * 360.0 - 180.0; - var lonMax = (x + 1) / n * 360.0 - 180.0; - - var latMaxRad = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * y / n))); - var latMax = latMaxRad * 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, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1"); - request.Content = JsonContent.Create(new GraphClientRequest - { - Query = requestContent - }); - - var response = await _httpClient.SendAsync(request); - var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<StopTileResponse>>(); - - 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"); - } - - var tileDef = new NetTopologySuite.IO.VectorTiles.Tiles.Tile(x, y, z); - VectorTile vt = new() { TileId = tileDef.Id }; - var stopsLayer = new Layer { Name = "stops" }; - - responseBody.Data?.StopsByBbox?.ForEach(stop => - { - var idParts = stop.GtfsId.Split(':', 2); - string feedId = idParts[0]; - string codeWithinFeed = _feedService.NormalizeStopCode(feedId, stop.Code ?? string.Empty); - - if (_feedService.IsStopHidden($"{feedId}:{codeWithinFeed}")) - { - return; - } - - // TODO: Duplicate from ArrivalsController - var (Color, TextColor) = _feedService.GetFallbackColourForFeed(idParts[0]); - var distinctRoutes = GetDistinctRoutes(feedId, stop.Routes ?? []); - - Feature feature = new() - { - Geometry = new NetTopologySuite.Geometries.Point(stop.Lon, stop.Lat), - Attributes = new AttributesTable - { - // The ID will be used to request the arrivals - { "id", stop.GtfsId }, - // The feed is the first part of the GTFS ID - { "feed", idParts[0] }, - // The public identifier, usually feed:code or feed:id, recognisable by users and in other systems - { "code", $"{idParts[0]}:{codeWithinFeed}" }, - { "name", _feedService.NormalizeStopName(feedId, stop.Name) }, - { "icon", GetIconNameForFeed(feedId) }, - { "transitKind", GetTransitKind(feedId) }, - // Routes - { "routes", JsonSerializer - .Serialize( - distinctRoutes.Select(r => { - var colour = r.Color ?? Color; - string textColour; - - 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 - }; - })) - } - } - }; - - stopsLayer.Features.Add(feature); - }); - - vt.Layers.Add(stopsLayer); - - using var ms = new MemoryStream(); - vt.Write(ms, minLinealExtent: 1, minPolygonalExtent: 2); - - _cache.Set($"stops-tile-{z}-{x}-{y}", ms.ToArray(), TimeSpan.FromMinutes(15)); - Response.Headers.Append("X-Cache-Hit", "false"); - - return File(ms.ToArray(), "application/x-protobuf"); - } - - private string GetIconNameForFeed(string feedId) - { - return feedId switch - { - "vitrasa" => "stop-vitrasa", - "santiago" => "stop-santiago", - "coruna" => "stop-coruna", - "xunta" => "stop-xunta", - "renfe" => "stop-renfe", - "feve" => "stop-feve", - _ => "stop-generic", - }; - } - - private string GetTransitKind(string feedId) - { - return feedId switch - { - "vitrasa" or "santiago" or "coruna" => "bus", - "xunta" => "coach", - "renfe" or "feve" => "train", - _ => "unknown", - }; - } - - private 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 = _feedService.GetUniqueRouteShortName(feedId, route.ShortName ?? string.Empty); - 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) - )]; - } -} |
