From a304c24b32c0327436bbd8c2853e60668e161b42 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 29 Dec 2025 00:41:52 +0100 Subject: Rename a lot of stuff, add Santiago real time --- .../Controllers/ServicesController.cs | 358 --------------------- 1 file changed, 358 deletions(-) delete mode 100644 src/Costasdev.Busurbano.ServiceViewer/Controllers/ServicesController.cs (limited to 'src/Costasdev.Busurbano.ServiceViewer/Controllers/ServicesController.cs') diff --git a/src/Costasdev.Busurbano.ServiceViewer/Controllers/ServicesController.cs b/src/Costasdev.Busurbano.ServiceViewer/Controllers/ServicesController.cs deleted file mode 100644 index bfb2a99..0000000 --- a/src/Costasdev.Busurbano.ServiceViewer/Controllers/ServicesController.cs +++ /dev/null @@ -1,358 +0,0 @@ -using Costasdev.ServiceViewer.Data; -using Costasdev.ServiceViewer.Data.Gtfs; -using Costasdev.ServiceViewer.Data.Gtfs.Enums; -using Costasdev.ServiceViewer.Data.QueryExtensions; -using Costasdev.ServiceViewer.Views.Services; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Costasdev.ServiceViewer.Controllers; - -[Route("")] -public class ServicesController : Controller -{ - private readonly AppDbContext _db; - - public ServicesController(AppDbContext db) - { - _db = db; - } - - [HttpGet("")] - public async Task DaysInFeed() - { - // FIXME: Use calendar too, but it requires getting the feed information - var days = await _db.CalendarDates - .Where(cd => cd.ExceptionType == ExceptionType.Added) - .Select(cd => cd.Date) - .Distinct() - .OrderBy(d => d) - .ToListAsync(); - - var model = new DaysInFeedModel - { - Days = days, - Today = DateOnly.FromDateTime(DateTime.Now), - }; - - return View(model); - } - - [HttpGet("{day}")] - public IActionResult ServicesInDay( - [FromRoute] string day - ) - { - var dateParsed = DateOnly.TryParseExact(day, "yyyy-MM-dd", out var dateOnly); - if (!dateParsed) - { - return BadRequest("Invalid date format. Please use 'yyyy-MM-dd'."); - } - - var dateTime = dateOnly.ToDateTime(TimeOnly.MinValue); - - // 1. Get all the calendars running that day - var dayOfWeek = dateOnly.DayOfWeek; - - var calendars = _db.Calendars - .WhereDayOfWeek(dayOfWeek) - .ToList(); - - var calendarDates = _db.CalendarDates - .Where(cd => cd.Date.Date == dateTime.Date) - .ToList(); - - // 2. Combine the two lists - HashSet activeServiceIds = []; - foreach (var calendar in calendars) - { - if (calendarDates.All(cd => - cd.ServiceId != calendar.ServiceId || cd.ExceptionType != ExceptionType.Removed)) - { - activeServiceIds.Add(calendar.ServiceId); - } - } - - foreach (var calendarDate in calendarDates.Where(cd => cd.ExceptionType == ExceptionType.Added)) - { - activeServiceIds.Add(calendarDate.ServiceId); - } - - // 3. Get the trips for those services - var tripsByService = _db.Trips - .AsSplitQuery() - .Include(t => t.Route) - .Where(t => activeServiceIds.Contains(t.ServiceId)) - .GroupBy(t => t.ServiceId) - .ToDictionary(g => g.Key, g => g.ToList()); - - /* - * For each shift, we extract the trip sequence number from the trip_id, order them ascending and take first - * one's first stop_time departure_time as shift start time and last one's last stop_time arrival_time as shift end time - * FIXME: Heuristic only for Vitrasa, not other feeds - * A 01LP001_008001_2, A 01LP001_008001_3, A 01LP001_008001_4, A 01LP001_008001_5... - */ - List serviceInformations = []; - List tripsWhoseFirstStopWeWant = []; - List tripsWhoseLastStopWeWant = []; - foreach (var (service, trips) in tripsByService) - { - var orderedTrips = trips - .Select(t => new - { - Trip = t, - Sequence = int.TryParse(t.Id.Split('_').LastOrDefault(), out var seq) ? seq : int.MaxValue - }) - .OrderBy(t => t.Sequence) - .ThenBy(t => t.Trip.TripHeadsign) // Secondary sort to ensure consistent ordering - .Select(t => t.Trip) - .ToList(); - - if (orderedTrips.Count == 0) - { - continue; - } - - tripsWhoseFirstStopWeWant.Add(orderedTrips.First().Id); - tripsWhoseLastStopWeWant.Add(orderedTrips.Last().Id); - serviceInformations.Add(new ServiceInformation( - service, - GetNameForServiceId(service), - orderedTrips, - orderedTrips.First(), - orderedTrips.Last() - )); - } - - var firstStopTimePerTrip = _db.StopTimes - .AsSplitQuery().AsNoTracking() - .Where(st => tripsWhoseFirstStopWeWant.Contains(st.TripId)) - .OrderBy(st => st.StopSequence) - .GroupBy(st => st.TripId) - .Select(g => g.First()) - .ToDictionary(st => st.TripId, st => st.Departure); - - var lastStopTimePerTrip = _db.StopTimes - .AsSplitQuery().AsNoTracking() - .Where(st => tripsWhoseLastStopWeWant.Contains(st.TripId)) - .OrderByDescending(st => st.StopSequence) - .GroupBy(st => st.TripId) - .Select(g => g.First()) - .ToDictionary(st => st.TripId, st => st.Arrival); - - // 4. Create a view model - List serviceCards = []; - foreach (var serviceInfo in serviceInformations) - { - // For lines 16-24 switching during the day we want (16, 2), (24,2), (16,1), (24,2)... in sequence - // TODO: Fix getting the trip sequence for any operator - var tripsOrdered = serviceInfo.Trips - .OrderBy(t => int.Parse(t.Id.Split('_').LastOrDefault() ?? string.Empty)) - .ToList(); - - List tripGroups = []; - GtfsRoute currentRoute = tripsOrdered.First().Route; - int currentRouteCount = 1; - foreach (var trip in tripsOrdered.Skip(1)) - { - if (trip.Route.Id == currentRoute.Id) - { - currentRouteCount++; - } - else - { - tripGroups.Add(new TripGroup(currentRoute, currentRouteCount)); - currentRoute = trip.Route; - currentRouteCount = 1; - } - } - - tripGroups.Add(new TripGroup(currentRoute, currentRouteCount)); - - serviceCards.Add(new ServicesInDayItem( - serviceInfo.ServiceId, - serviceInfo.ServiceName, - serviceInfo.Trips, - tripGroups, - firstStopTimePerTrip.TryGetValue(serviceInfo.FirstTrip.Id, out var shiftStart) - ? shiftStart - : string.Empty, - lastStopTimePerTrip.TryGetValue(serviceInfo.LastTrip.Id, out var shiftEnd) ? shiftEnd : string.Empty - )); - } - - return View(new ServiceInDayModel - { - Items = serviceCards, - Date = dateOnly - }); - } - - [HttpGet("{day}/{serviceId}")] - public IActionResult ServiceDetails( - [FromRoute] string day, - [FromRoute] string serviceId - ) - { - #region Validation - - var dateParsed = DateOnly.TryParseExact(day, "yyyy-MM-dd", out var dateOnly); - if (!dateParsed) - { - return BadRequest("Invalid date format. Please use 'yyyy-MM-dd'."); - } - - var dateTime = dateOnly.ToDateTime(TimeOnly.MinValue); - - // 1. Get all the calendars running that day - var dayOfWeek = dateOnly.DayOfWeek; - - var calendars = _db.Calendars - .WhereDayOfWeek(dayOfWeek) - .ToList(); - - var calendarDates = _db.CalendarDates - .Where(cd => cd.Date.Date == dateTime.Date) - .ToList(); - - // 2. Combine the two lists - HashSet activeServiceIds = []; - foreach (var calendar in calendars) - { - if (calendarDates.All(cd => - cd.ServiceId != calendar.ServiceId || cd.ExceptionType != ExceptionType.Removed)) - { - activeServiceIds.Add(calendar.ServiceId); - } - } - - foreach (var calendarDate in calendarDates.Where(cd => cd.ExceptionType == ExceptionType.Added)) - { - activeServiceIds.Add(calendarDate.ServiceId); - } - - if (!activeServiceIds.Contains(serviceId)) - { - return NotFound("Service not found for the given day."); - } - - #endregion - - var trips = _db.Trips - .AsSplitQuery() - .Include(t => t.Route) - .Where(t => t.ServiceId == serviceId) - .ToList(); - - var orderedTrips = trips - .Select(t => new - { - Trip = t, - Sequence = int.TryParse(t.Id.Split('_').LastOrDefault(), out var seq) ? seq : int.MaxValue - }) - .OrderBy(t => t.Sequence) - .ThenBy(t => t.Trip.TripHeadsign) // Secondary sort to ensure consistent ordering - .Select(t => t.Trip) - .ToList(); - - List items = []; - int totalDistance = 0; - TimeSpan totalDrivingMinutes = TimeSpan.Zero; - foreach (var trip in orderedTrips) - { - var stopTimes = _db.StopTimes - .AsSplitQuery().AsNoTracking() - .Include(gtfsStopTime => gtfsStopTime.GtfsStop) - .Where(st => st.TripId == trip.Id) - .OrderBy(st => st.StopSequence) - .ToList(); - - if (stopTimes.Count == 0) - { - continue; - } - - var firstStop = stopTimes.First(); - var lastStop = stopTimes.Last(); - - var tripDistance = (int?)(lastStop.ShapeDistTraveled - firstStop.ShapeDistTraveled); - totalDistance += tripDistance ?? 0; - totalDrivingMinutes += (lastStop.ArrivalTime - firstStop.DepartureTime); - - items.Add(new ServiceDetailsItem - { - TripId = trip.Id, - SafeRouteId = trip.Route.SafeId, - ShortName = trip.Route.ShortName, - LongName = trip.TripHeadsign ?? trip.Route.LongName, - TotalDistance = tripDistance.HasValue ? $"{tripDistance.Value/1_000:F2} km" : "N/A", - FirstStopName = firstStop.GtfsStop.Name, - FirstStopTime = firstStop.Departure, - LastStopName = lastStop.GtfsStop.Name, - LastStopTime = lastStop.Arrival - }); - } - - return View(new ServiceDetailsModel - { - Date = dateOnly, - ServiceId = serviceId, - ServiceName = GetNameForServiceId(serviceId), - TotalDrivingTime = totalDrivingMinutes, - TotalDistance = totalDistance, - Items = items - }); - } - - private string GetNameForServiceId(string serviceId) - { - var serviceIndicator = serviceId[^6..]; // "008001" or "202006" - if (string.IsNullOrEmpty(serviceIndicator)) - { - return serviceId; - } - - var lineNumber = int.Parse(serviceIndicator[..3]); // "008" - var shiftNumber = int.Parse(serviceIndicator[3..]); // "001" - var lineName = lineNumber switch - { - 1 => "C1", - 3 => "C3", - 30 => "N1", - 33 => "N4", - 8 => "A", - 101 => "H", - 201 => "U1", - 202 => "U2", - 150 => "REF", - 500 => "TUR", - _ => $"L{lineNumber}" - }; - - return $"Servicio {lineName}-{shiftNumber}º ({serviceId[^6..]})"; - } -} - -internal class ServiceInformation -{ - internal string ServiceId { get; } - public string ServiceName { get; set; } - internal List Trips { get; } - internal GtfsTrip FirstTrip { get; } - internal GtfsTrip LastTrip { get; } - - internal ServiceInformation( - string serviceId, - string serviceName, - List trips, - GtfsTrip firstTrip, - GtfsTrip lastTrip - ) - { - ServiceId = serviceId; - ServiceName = serviceName; - Trips = trips; - FirstTrip = firstTrip; - LastTrip = lastTrip; - } -} -- cgit v1.3