mod arrival; mod loader; mod utils; mod refresher; mod realtime; pub mod structs; use chrono::{DateTime, Local, Timelike}; use log::{debug, trace, warn}; use std::{collections::{HashMap, HashSet}, fs::File }; use gtfs_structures::{Exception, RawTrip}; use crate::gtfs::{loader::load_gtfs, structs::{Arrival, Gtfs, Preferences, Error}}; impl Gtfs<'_> { pub fn get_next_arrivals_for(&self, target_datetime: &DateTime) -> Option>> { let naive_target = target_datetime.naive_local(); let target_date = naive_target.date(); // Find which calendars apply debug!("Looking for calendars that apply to date {:#?}", target_date); let mut active_service_ids: HashSet = HashSet::new(); for (id, calendar) in self.calendar.iter() { if calendar.valid_weekday(target_date) && calendar.start_date <= target_date && calendar.end_date > target_date { active_service_ids.insert(id.to_string()); debug!("Matched calendar: {:#?}", calendar); } } debug!("Found {} services active today", active_service_ids.len()); // Are there any exceptions for the calendars above? for (_calendar_id, exceptions) in self.calendar_dates.iter() { for exception in exceptions.iter() { if exception.date.eq(&target_date) { match exception.exception_type { Exception::Added => { active_service_ids.insert(exception.service_id.clone()); } Exception::Deleted => { active_service_ids.remove(&exception.service_id); } } } } } debug!("After exceptions, there are now {} services active: {:#?}", active_service_ids.len(), active_service_ids); // Find the trips happening on these calendars let mut trips: HashMap<&String, &RawTrip> = HashMap::new(); for (_id, trip) in self.trips.iter() { if active_service_ids.contains(&trip.service_id) { trips.insert(&trip.id, trip); } } debug!("Found {} trips", trips.len()); // Finally, find the arrivals for the active trips on the chosen stops let mut arrivals: Vec = Vec::new(); // Stop times are parsed as the number of seconds since midnight on the current day let current_timestamp = target_datetime.time().hour() * 3600 + target_datetime.time().minute() * 60 + target_datetime.time().second(); for (_id, stop_time) in self.stop_times.iter() { if trips.contains_key(&stop_time.trip_id) { let stop_timestamp = stop_time.departure_time.or(stop_time.arrival_time)?; let trip= &self.trips.get(&stop_time.trip_id).unwrap(); if current_timestamp < stop_timestamp.into() { let arrival: Arrival = Arrival { route: self.routes.get(&self.trips.get(&stop_time.trip_id)?.route_id)?, stop: self.stops.get(&stop_time.stop_id)?, stop_time: stop_time, trip: &trip, departure_time: stop_timestamp.into() }; trace!("{:#?}: Arrival to {:#?} for trip ID {:#?}.", format!("{:02}:{:02}", (arrival.departure_time/3600) as u32, ((arrival.departure_time / 60) % 60) as u32), arrival.trip.trip_headsign.as_ref().unwrap(), arrival.trip.id); arrivals.push(arrival); } } } arrivals.sort(); debug!("Found {} arrivals", arrivals.len()); // Update real-time deltas let realtime_result = self.realtime_update(&mut arrivals); if realtime_result.is_err() { warn!("Unable to update realtime arrivals: {:#?}", realtime_result.err().unwrap()._message) } return Some(arrivals); } /// Load a GTFS structure from a zip file pub fn load(prefs: &Preferences) -> Result, Error> { _ = refresher::refresh(prefs); // Open zip file let zip_file = File::open(prefs.gtfs_file_path()?)?; let mut zip_reader = zip::ZipArchive::new(zip_file)?; let mut gtfs: Gtfs = Gtfs { preferences: prefs, agencies: Vec::new(), calendar: HashMap::new(), calendar_dates: HashMap::new(), routes: HashMap::new(), stops: HashMap::new(), trips: HashMap::new(), stop_times: HashMap::new(), }; load_gtfs(&mut gtfs, &mut zip_reader, &prefs.route_numbers, &prefs.stop_codes); return Ok(gtfs); } }