mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
147 lines
3.9 KiB
Go
147 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"time"
|
|
|
|
"bugulma/backend/internal/domain"
|
|
"bugulma/backend/internal/repository"
|
|
)
|
|
|
|
// Departure describes a single upcoming departure event
|
|
type Departure struct {
|
|
Time time.Time `json:"time"`
|
|
TripID string `json:"trip_id"`
|
|
RouteID string `json:"route_id"`
|
|
RouteShort string `json:"route_short_name"`
|
|
StopID string `json:"stop_id"`
|
|
}
|
|
|
|
// ScheduleService handles GTFS schedule queries
|
|
type ScheduleService struct {
|
|
gtfsRepo *repository.PublicTransportGTFSRepository
|
|
ptRepo domain.PublicTransportRepository
|
|
}
|
|
|
|
func NewScheduleService(gtfsRepo *repository.PublicTransportGTFSRepository, ptRepo domain.PublicTransportRepository) *ScheduleService {
|
|
return &ScheduleService{gtfsRepo: gtfsRepo, ptRepo: ptRepo}
|
|
}
|
|
|
|
// GetNextDepartures returns the next departures for a stop starting from fromTime
|
|
func (s *ScheduleService) GetNextDepartures(ctx context.Context, stopID string, fromTime time.Time, limit int) ([]Departure, error) {
|
|
// Load stop_times for the stop
|
|
stopTimes, err := s.gtfsRepo.ListStopTimesByStop(ctx, stopID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build a map of serviceIDs active today
|
|
today := fromTime.UTC()
|
|
activeServices := map[string]bool{}
|
|
// For each stop_time, check trip->service calendar
|
|
// To keep this efficient, we'll iterate stopTimes and inspect their trip's service via DB
|
|
var departures []Departure
|
|
|
|
for _, st := range stopTimes {
|
|
// quick filter by departure secs
|
|
if st.DepartureSecs < secondsSinceMidnight(fromTime) {
|
|
continue
|
|
}
|
|
|
|
// get trip
|
|
trip, err := s.gtfsRepo.GetTripByID(ctx, st.TripID)
|
|
if err != nil || trip == nil {
|
|
continue
|
|
}
|
|
|
|
// check service calendar
|
|
svcID := trip.ServiceID
|
|
if svcID == "" {
|
|
continue
|
|
}
|
|
|
|
if !activeServices[svcID] {
|
|
// check calendar
|
|
cal, err := s.gtfsRepo.GetCalendarByServiceID(ctx, svcID)
|
|
active := false
|
|
if err == nil && cal != nil {
|
|
yy, mm, dd := today.Date()
|
|
todayDate := time.Date(yy, mm, dd, 0, 0, 0, 0, time.UTC)
|
|
if !todayDate.Before(cal.StartDate) && !todayDate.After(cal.EndDate) {
|
|
weekday := today.Weekday()
|
|
switch weekday {
|
|
case time.Monday:
|
|
active = cal.Monday
|
|
case time.Tuesday:
|
|
active = cal.Tuesday
|
|
case time.Wednesday:
|
|
active = cal.Wednesday
|
|
case time.Thursday:
|
|
active = cal.Thursday
|
|
case time.Friday:
|
|
active = cal.Friday
|
|
case time.Saturday:
|
|
active = cal.Saturday
|
|
case time.Sunday:
|
|
active = cal.Sunday
|
|
}
|
|
}
|
|
}
|
|
// TODO: consider calendar_dates exceptions (not implemented yet)
|
|
if active {
|
|
activeServices[svcID] = true
|
|
} else {
|
|
activeServices[svcID] = false
|
|
}
|
|
}
|
|
|
|
if !activeServices[svcID] {
|
|
continue
|
|
}
|
|
|
|
// Get route for trip
|
|
var route domain.PublicTransportRoute
|
|
if rt, err := s.gtfsRepo.GetRouteByTripID(ctx, st.TripID); err == nil && rt != nil {
|
|
route = *rt
|
|
}
|
|
|
|
// compute departure time as today's date + departure secs
|
|
dep := secondsToTimeUTC(fromTime, st.DepartureSecs)
|
|
departures = append(departures, Departure{
|
|
Time: dep,
|
|
TripID: st.TripID,
|
|
RouteID: route.ID,
|
|
RouteShort: route.ShortName,
|
|
StopID: st.StopID,
|
|
})
|
|
}
|
|
|
|
// For frequencies we would need to expand entries - TODO: support frequencies expansion in future
|
|
|
|
// Sort departures by time
|
|
sort.Slice(departures, func(i, j int) bool {
|
|
return departures[i].Time.Before(departures[j].Time)
|
|
})
|
|
|
|
if limit > 0 && len(departures) > limit {
|
|
departures = departures[:limit]
|
|
}
|
|
|
|
return departures, nil
|
|
}
|
|
|
|
func secondsSinceMidnight(t time.Time) int {
|
|
h := t.Hour()
|
|
m := t.Minute()
|
|
s := t.Second()
|
|
return h*3600 + m*60 + s
|
|
}
|
|
|
|
func secondsToTimeUTC(base time.Time, secs int) time.Time {
|
|
// compute midnight of base date then add secs (handle > 86400)
|
|
y, mo, d := base.Date()
|
|
midnight := time.Date(y, mo, d, 0, 0, 0, 0, time.UTC)
|
|
return midnight.Add(time.Duration(secs) * time.Second)
|
|
}
|