cleanup and filter support
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 39s

This commit is contained in:
Nicholas Orlowsky 2026-02-21 15:26:48 -05:00
parent 6773e6ae30
commit 3f68335eb4
No known key found for this signature in database
GPG key ID: A9F3BA4C0AA7A70B
62 changed files with 2364 additions and 1901 deletions

View file

@ -1,236 +0,0 @@
use chrono_tz::America::New_York;
use libseptastic::{direction::Direction, stop_schedule::{Trip, TripTracking}};
use std::{cmp::Ordering, collections::BTreeMap};
use serde::{Serialize};
use libseptastic::stop_schedule::TripTracking::Tracked;
use chrono::Timelike;
#[derive(askama::Template)]
#[template(path = "layout.html")]
pub struct ContentTemplate<T: askama::Template> {
pub content: T,
pub page_title: Option<String>,
pub page_desc: Option<String>,
pub load_time_ms: Option<u128>,
pub widescreen: bool
}
#[derive(askama::Template)]
#[template(path = "route.html")]
pub struct RouteTemplate {
pub route: libseptastic::route::Route,
pub timetables: Vec<TimetableDirection>,
pub filter_stops: Option<Vec<String>>
}
#[derive(askama::Template)]
#[template(path = "routes.html")]
pub struct RoutesTemplate {
pub rr_routes: Vec<libseptastic::route::Route>,
pub subway_routes: Vec<libseptastic::route::Route>,
pub trolley_routes: Vec<libseptastic::route::Route>,
pub bus_routes: Vec<libseptastic::route::Route>
}
#[derive(askama::Template)]
#[template(path = "stops.html")]
pub struct StopsTemplate {
pub tc_stops: Vec<libseptastic::stop::Stop>,
}
#[derive(askama::Template)]
#[template(path = "index.html")]
pub struct IndexTemplate {
}
#[derive(Debug, Serialize)]
pub struct TimetableStopRow {
pub stop_id: String,
pub stop_name: String,
pub stop_sequence: i64,
pub times: Vec<Option<i64>>
}
#[derive(Debug, Serialize)]
pub struct TimetableDirection {
pub direction: Direction,
pub trip_ids: Vec<String>,
pub tracking_data: Vec<TripTracking>,
pub rows: Vec<TimetableStopRow>,
pub next_id: Option<String>
}
pub struct TripPerspective {
pub trip:libseptastic::stop_schedule::Trip,
pub perspective_stop: libseptastic::stop_schedule::StopSchedule,
pub est_arrival_time: i64,
pub is_tracked: bool
}
#[derive(askama::Template)]
#[template(path = "stop.html")]
pub struct StopTemplate {
pub stop: libseptastic::stop::Stop,
pub routes: Vec<libseptastic::route::Route>,
pub trips: Vec<TripPerspective>,
pub current_time: i64
}
#[derive(askama::Template)]
#[template(path = "stop_table_impl.html")]
pub struct StopTableTemplate {
pub trips: Vec<TripPerspective>,
pub current_time: i64
}
pub fn build_timetables(
directions: Vec<Direction>,
trips: Vec<Trip>,
) -> Vec<TimetableDirection> {
let mut results = Vec::new();
for direction in directions {
let now_utc = chrono::Utc::now();
let now = now_utc.with_timezone(&New_York);
let naive_time = now.time();
let seconds_since_midnight = naive_time.num_seconds_from_midnight();
let mut next_id: Option<String> = None;
let mut direction_trips: Vec<&Trip> = trips
.iter()
.filter(|trip| trip.direction.direction == direction.direction)
.collect();
direction_trips.sort_by_key(|trip| {
trip.schedule
.iter()
.filter_map(|s| Some(s.arrival_time))
.min()
.unwrap_or(i64::MAX)
});
for trip in direction_trips.clone() {
if let Some(last) = trip.schedule.iter().max_by_key(|x| x.arrival_time) {
if next_id == None && i64::from(seconds_since_midnight) < last.arrival_time {
next_id = Some(last.stop.id.to_string());
}
}
}
let trip_ids: Vec<String> = direction_trips
.iter()
.map(|t| t.trip_id.clone())
.collect();
let live_trips: Vec<TripTracking> = direction_trips
.iter()
.map(|t| t.tracking_data.clone())
.collect();
let mut stop_map: BTreeMap<String, (i64, String, Vec<Option<i64>>)> = BTreeMap::new();
for (trip_index, trip) in direction_trips.iter().enumerate() {
for stop in &trip.schedule {
let entry = stop_map
.entry(stop.stop.id.clone())
.or_insert((stop.stop_sequence, stop.stop.name.clone(), vec![None; direction_trips.len()]));
// If this stop_id appears in multiple trips with different sequences, keep the lowest
entry.0 = entry.0.max(stop.stop_sequence);
entry.1 = stop.stop.name.clone();
entry.2[trip_index] = Some(stop.arrival_time);
}
}
let mut rows: Vec<TimetableStopRow> = stop_map
.into_iter()
.map(|(stop_id, (stop_sequence, stop_name, times))| TimetableStopRow {
stop_id,
stop_sequence,
stop_name,
times,
})
.collect();
rows.sort_by(| a, b| {
if a.stop_sequence < b.stop_sequence {
Ordering::Less
} else {
Ordering::Greater
}
});
results.push(TimetableDirection {
direction: direction.clone(),
trip_ids,
rows,
tracking_data: live_trips ,
next_id
});
}
results
}
mod filters {
pub fn format_load_time(
nanos: &u128,
_: &dyn askama::Values,
) -> askama::Result<String> {
if *nanos >= 1000000000 {
return Ok(format!("{}s", (nanos/1000000000)));
} else if *nanos >= 1000000 {
return Ok(format!("{}ms", nanos/1000000));
} if *nanos >= 1000 {
return Ok(format!("{}us", nanos/1000));
} else {
return Ok(format!("{}ns", nanos));
}
}
pub fn format_time(
seconds_since_midnight: &i64,
_: &dyn askama::Values,
) -> askama::Result<String> {
let total_minutes = seconds_since_midnight / 60;
let (hours, ampm) = {
let hrs = total_minutes / 60;
if hrs > 12 {
(hrs - 12, "PM")
} else if hrs == 12 {
(12, "PM")
} else if hrs > 0 {
(hrs, "AM")
} else {
(12, "AM")
}
};
let minutes = total_minutes % 60;
Ok(format!("{}:{:02} {}", hours, minutes, ampm))
}
pub fn format_time_with_seconds(
seconds_since_midnight: &i64,
_: &dyn askama::Values,
) -> askama::Result<String> {
let total_minutes = seconds_since_midnight / 60;
let (hours, ampm) = {
let hrs = total_minutes / 60;
if hrs > 12 {
(hrs - 12, "PM")
} else if hrs == 12 {
(12, "PM")
} else if hrs > 0 {
(hrs, "AM")
} else {
(12, "AM")
}
};
let minutes = total_minutes % 60;
let seconds = seconds_since_midnight % 60;
Ok(format!("{}:{:02}:{:02} {}", hours, minutes, seconds, ampm))
}
}