cleanup and filter support
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 39s
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 39s
This commit is contained in:
parent
6773e6ae30
commit
3f68335eb4
62 changed files with 2364 additions and 1901 deletions
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue