diff options
Diffstat (limited to 'src/Enmarcha.Backend/Controllers')
| -rw-r--r-- | src/Enmarcha.Backend/Controllers/RoutePlannerController.cs | 93 | ||||
| -rw-r--r-- | src/Enmarcha.Backend/Controllers/TileController.cs | 2 |
2 files changed, 91 insertions, 4 deletions
diff --git a/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs b/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs index c7201b0..7a03a24 100644 --- a/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs +++ b/src/Enmarcha.Backend/Controllers/RoutePlannerController.cs @@ -4,7 +4,9 @@ using Enmarcha.Sources.OpenTripPlannerGql.Queries; using Enmarcha.Backend.Configuration; using Enmarcha.Backend.Services; using Enmarcha.Backend.Types.Planner; +using FuzzySharp; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; namespace Enmarcha.Backend.Controllers; @@ -18,13 +20,19 @@ public partial class RoutePlannerController : ControllerBase private readonly IGeocodingService _geocodingService; private readonly AppConfiguration _config; private readonly HttpClient _httpClient; + private readonly IMemoryCache _cache; + private readonly FeedService _feedService; + + private const string GaliciaBounds = "-9.3,43.8,-6.7,41.7"; public RoutePlannerController( ILogger<RoutePlannerController> logger, OtpService otpService, IGeocodingService geocodingService, IOptions<AppConfiguration> config, - HttpClient httpClient + HttpClient httpClient, + IMemoryCache cache, + FeedService feedService ) { _logger = logger; @@ -32,6 +40,8 @@ public partial class RoutePlannerController : ControllerBase _geocodingService = geocodingService; _config = config.Value; _httpClient = httpClient; + _cache = cache; + _feedService = feedService; } [HttpGet("autocomplete")] @@ -42,8 +52,33 @@ public partial class RoutePlannerController : ControllerBase return BadRequest("Query cannot be empty"); } - var results = await _geocodingService.GetAutocompleteAsync(query); - return Ok(results); + var nominatimTask = _geocodingService.GetAutocompleteAsync(query); + var stopsTask = GetCachedStopsAsync(); + + await Task.WhenAll(nominatimTask, stopsTask); + + var nominatimResults = await nominatimTask; + var allStops = await stopsTask; + + // Fuzzy search stops + var fuzzyResults = Process.ExtractSorted( + query, + allStops.Select(s => s.Name ?? string.Empty), + cutoff: 60 + ).Take(5).Select(r => allStops[r.Index]).ToList(); + + // Merge results: stops first, then nominatim, deduplicating by coordinates (approx) + var finalResults = new List<PlannerSearchResult>(fuzzyResults); + + foreach (var res in nominatimResults) + { + if (!finalResults.Any(f => Math.Abs(f.Lat - res.Lat) < 0.0001 && Math.Abs(f.Lon - res.Lon) < 0.0001)) + { + finalResults.Add(res); + } + } + + return Ok(finalResults); } [HttpGet("reverse")] @@ -99,4 +134,56 @@ public partial class RoutePlannerController : ControllerBase [LoggerMessage(LogLevel.Error, "Error fetching route planning, received {statusCode} {responseBody}")] partial void LogErrorFetchingRoutes(HttpStatusCode? statusCode, string responseBody); + + private async Task<List<PlannerSearchResult>> GetCachedStopsAsync() + { + const string cacheKey = "otp_all_stops"; + if (_cache.TryGetValue(cacheKey, out List<PlannerSearchResult>? cachedStops) && cachedStops != null) + { + return cachedStops; + } + + try + { + // Galicia bounds: minLon, minLat, maxLon, maxLat + var bbox = new StopTileRequestContent.Bbox(-9.3, 41.7, -6.7, 43.8); + var query = StopTileRequestContent.Query(bbox); + + var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.OpenTripPlannerBaseUrl}/gtfs/v1"); + request.Content = JsonContent.Create(new GraphClientRequest { Query = query }); + + var response = await _httpClient.SendAsync(request); + var responseBody = await response.Content.ReadFromJsonAsync<GraphClientResponse<StopTileResponse>>(); + + if (responseBody is not { IsSuccess: true } || responseBody.Data?.StopsByBbox == null) + { + _logger.LogError("Error fetching stops from OTP for caching"); + return new List<PlannerSearchResult>(); + } + + var stops = responseBody.Data.StopsByBbox.Select(s => + { + var feedId = s.GtfsId.Split(':')[0]; + var name = _feedService.NormalizeStopName(feedId, s.Name); + var code = _feedService.NormalizeStopCode(feedId, s.Code ?? string.Empty); + + return new PlannerSearchResult + { + Name = name, + Label = string.IsNullOrWhiteSpace(code) ? name : $"{name} ({code})", + Lat = s.Lat, + Lon = s.Lon, + Layer = "stop" + }; + }).ToList(); + + _cache.Set(cacheKey, stops, TimeSpan.FromHours(18)); + return stops; + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception fetching stops from OTP for caching"); + return new List<PlannerSearchResult>(); + } + } } diff --git a/src/Enmarcha.Backend/Controllers/TileController.cs b/src/Enmarcha.Backend/Controllers/TileController.cs index 3459997..4065ecd 100644 --- a/src/Enmarcha.Backend/Controllers/TileController.cs +++ b/src/Enmarcha.Backend/Controllers/TileController.cs @@ -186,7 +186,7 @@ public class TileController : ControllerBase "vitrasa" or "tussa" or "tranvias" => "bus", "xunta" => "coach", "renfe" or "feve" => "train", - _ => "unknown", + _ => throw new ArgumentException("Feed ID not a known type", feedId) }; } |
