diff --git a/.gitignore b/.gitignore index 4c49bd7..7edecbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .env +tracking_data* +target/ + diff --git a/web/src/controllers/route.rs b/web/src/controllers/route.rs index 3e0f188..8fd1062 100644 --- a/web/src/controllers/route.rs +++ b/web/src/controllers/route.rs @@ -6,9 +6,9 @@ use actix_web::{ HttpResponse, Responder, get, web::{self, Data}, }; -use libseptastic::{route::RouteType, stop_schedule::Trip}; +use libseptastic::{route::RouteType, stop::StopType, stop_schedule::Trip}; use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, sync::Arc}; +use std::{collections::{HashMap, HashSet}, io::Write, sync::Arc}; #[derive(Debug, Deserialize, Clone)] struct RouteQueryParams { diff --git a/web/src/main.rs b/web/src/main.rs index a42266b..ee076e3 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -5,10 +5,13 @@ use log::*; use services::gtfs_pull; use std::{fs::File, io::Read, sync::Arc}; +use crate::routing::{bfs_rts, construct_graph}; + mod controllers; mod services; mod session_middleware; mod templates; +mod routing; pub struct AppState { gtfs_service: services::gtfs_pull::GtfsPullService, @@ -58,6 +61,7 @@ async fn main() -> ::anyhow::Result<()> { .service(controllers::stop::get_stop_table_html) .service(controllers::index::get_index_html) .service(controllers::map::get_map_element_html) + .service(routing::get_routing_sample) .service(actix_files::Files::new("/assets", "./assets")) }) .bind(("0.0.0.0", 8080))? diff --git a/web/src/routing.rs b/web/src/routing.rs index 7c6e320..cff4df2 100644 --- a/web/src/routing.rs +++ b/web/src/routing.rs @@ -1,8 +1,10 @@ -use std::{cmp::Ordering, collections::{BTreeSet, HashMap, HashSet}}; +use std::{cmp::Ordering, collections::{BTreeSet, HashMap, HashSet}, sync::Arc}; +use actix_web::{Responder, web::Data}; +use libseptastic::stop::StopType; use log::info; -use crate::services; +use crate::{AppState, services, session_middleware::{SessionResponder, SessionResponse}, templates::RoutingTemplate}; pub struct RoutingNodePointer { pub stop_id: String, @@ -79,23 +81,24 @@ pub fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 { } -pub fn get_stops_near(cds: Coordinates, - all_stops: &HashMap +pub fn get_platforms_near(cds: Coordinates, + all_platforms: &HashMap> ) -> HashSet { let near_thresh_km = 0.45; - let mut stops: HashSet = HashSet::new(); + let mut platforms: HashSet = HashSet::new(); - for stop_p in all_stops { - let stop = stop_p.1; + for platform_p in all_platforms { + let platform = platform_p.1; - let dist = haversine_distance(cds.lat, cds.lng, stop.lat, stop.lng); + let dist = haversine_distance(cds.lat, cds.lng, platform.lat, platform.lng); if dist.abs() < near_thresh_km { - stops.insert(stop.id.clone()); + info!("dist {} for {} ({})", dist, platform.id.clone(), platform.name.clone()); + platforms.insert(platform.id.clone()); } } - stops + platforms } pub fn get_stop_as_node() { @@ -103,17 +106,17 @@ pub fn get_stop_as_node() { pub fn construct_graph( dest: Coordinates, - all_stops: &HashMap, + all_platforms: &HashMap>, gtfs_service: &services::gtfs_pull::GtfsPullService ) -> RoutingGraph { let mut graph = RoutingGraph::new(); - let limited_rts = vec!["44", "65", "27", "38", "124", "125", "1"]; + let limited_rts = vec!["SEPTABUS_44", "SEPTABUS_65", "SEPTABUS_27", "SEPTABUS_38", "SEPTABUS_124", "SEPTABUS_125", "SEPTABUS_1"]; - for stop_p in all_stops { - let stop = stop_p.1; + for platform_p in all_platforms { + let platform = platform_p.1; - let ras = gtfs_service.get_routes_at_stop(&stop.id); + let ras = gtfs_service.get_routes_at_stop(&platform.id); let cont = { let mut ret = false; @@ -130,24 +133,24 @@ pub fn construct_graph( continue; } - graph.insert(stop.id.clone(), RoutingNode { - stop_id: stop.id.clone(), - stop_name: stop.name.clone(), + graph.insert(platform.id.clone(), RoutingNode { + stop_id: platform.id.clone(), + stop_name: platform.name.clone(), next_stops_per_routes: { - let routes = gtfs_service.get_routes_at_stop(&stop.id); + let routes = gtfs_service.get_routes_at_stop(&platform.id); let mut other_stops = HashMap::>::new(); for route in &routes { - let mut stops = gtfs_service.get_stops_by_route(&route); - stops.remove(&stop.id); + let mut platforms = gtfs_service.get_platforms_by_route(&route); + platforms.remove(&platform.id); let rnps = { let mut ret = BTreeSet::new(); - for stop in &stops { - let stp = all_stops.get(stop).unwrap(); + for platform in &platforms { + let plt = all_platforms.get(platform).unwrap(); ret.insert(RoutingNodePointer{ - dest_dist: haversine_distance(dest.lat, dest.lng, stp.lat, stp.lng), - stop_id: stop.clone(), + dest_dist: haversine_distance(dest.lat, dest.lng, plt.lat, plt.lng), + stop_id: platform.clone(), route_id: route.clone(), stop_sequence: 0, direction: 0 @@ -167,23 +170,50 @@ pub fn construct_graph( graph } -pub fn bfs_rts_int(route_id: &String, origin: &String, graph: &RoutingGraph, dests: &HashSet, mut visited: HashSet, max_legs: u8) -> Option { + +#[derive(Clone)] +pub struct WalkRouteStep { + pub dist: f64, + pub dest: String +} + +#[derive(Clone)] +pub struct TransitRouteStep { + pub platform: String, + pub route: String +} + +#[derive(Clone)] +pub enum RouteStep { + Walk(WalkRouteStep), + Transit(TransitRouteStep), + Origin, + Destination +} + +pub fn bfs_rts_int(route_id: &String, origin: &String, graph: &RoutingGraph, dests: &HashSet, mut visited: HashSet, max_legs: u8) -> Option> { if max_legs == 0 { return None; } + let mut limited_rts = HashSet::new(); - for item in vec!["44", "65", "27", "38", "124", "125", "1"] { + + for item in vec!["SEPTABUS_44", "SEPTABUS_65", "SEPTABUS_27", "SEPTABUS_38", "SEPTABUS_124", "SEPTABUS_125", "SEPTABUS_1"] { limited_rts.insert(item); } - if !limited_rts.contains(&route_id.as_str()) { - return None; - } + if !limited_rts.contains(&route_id.as_str()) { + info!("{} is not in the allowlist", route_id.as_str()); + return None; + } + + info!("made it"); if let Some(origin_stop) = graph.get(origin) { - if dests.contains(origin) { - return Some(format!("[stop {} via rt {}] --> DEST", origin_stop.stop_name, route_id)) - } + if dests.contains(origin) { + return Some(vec![ RouteStep::Transit(TransitRouteStep { platform: origin_stop.stop_name.clone(), route: route_id.clone() }) , RouteStep::Destination]) + } + if visited.contains(origin) { return None; } @@ -196,8 +226,9 @@ pub fn bfs_rts_int(route_id: &String, origin: &String, graph: &RoutingGraph, des } for rnp in items.1 { - if let Some(rt) = bfs_rts_int(items.0, &rnp.stop_id, graph, dests, visited.clone(), max_legs - 1) { - return Some(format!("[stop {} via rt {}] >>[XFER]>> {}", origin_stop.stop_name, route_id, rt)) + if let Some(mut rt) = bfs_rts_int(items.0, &rnp.stop_id, graph, dests, visited.clone(), max_legs - 1).clone() { + rt.insert(0, RouteStep::Transit(TransitRouteStep{ platform: origin_stop.stop_name.clone(), route: route_id.clone()})); + return Some(rt) } } } @@ -206,16 +237,18 @@ pub fn bfs_rts_int(route_id: &String, origin: &String, graph: &RoutingGraph, des None } -pub fn bfs_rts(origin: &String, graph: &RoutingGraph, dests: &HashSet) -> String { - let mut resp = String::new(); +pub fn bfs_rts(origin: &String, graph: &RoutingGraph, dests: &HashSet) -> Vec> { + let mut resp = vec![]; if let Some(origin_stop) = graph.get(origin) { for items in &origin_stop.next_stops_per_routes { let route_id = items.0; for rnp in items.1 { - if let Some(rt) = bfs_rts_int(route_id, &rnp.stop_id, graph, dests, HashSet::new(), 3) { - resp += format!("ORIGIN --> [stop {} via rt {}] --> {}\n", origin_stop.stop_name, route_id, rt).as_str(); + if let Some(mut rt) = bfs_rts_int(route_id, &rnp.stop_id, graph, dests, HashSet::new(), 3).clone() { + rt.insert(0, RouteStep::Walk(WalkRouteStep { dist: 0.0, dest: origin_stop.stop_name.clone() })); + rt.insert(0, RouteStep::Origin); + resp.push(rt); } } } @@ -223,3 +256,32 @@ pub fn bfs_rts(origin: &String, graph: &RoutingGraph, dests: &HashSet) - resp } + +#[actix_web::get("/routing")] +pub async fn get_routing_sample( + state: Data>, + resp: SessionResponse, +) -> impl Responder { + let mut all_platforms = HashMap::new(); + state.gtfs_service.get_all_stops().iter().map(|stop| { + match &stop.1.platforms { + StopType::SinglePlatform(sp) => vec![sp.clone()], + StopType::MultiPlatform(mp) => mp.clone() + } + }).flatten().for_each(|p| { + all_platforms.insert((&p.id).clone(), p.clone()); + }); + + let srcs = get_platforms_near(Coordinates { lat: 39.95707, lng: -75.16625 }, &all_platforms); + let src = srcs.iter().next().unwrap(); + let dests = get_platforms_near(Coordinates { lat: 40.009144, lng: -75.213796 }, &all_platforms); + let rg = construct_graph(Coordinates { lat: 40.009144, lng: -75.213796 }, &all_platforms, &state.gtfs_service); + + resp.respond( + "test", + "test", + RoutingTemplate { + trips: bfs_rts(&String::from("SEPTABUS_3057"), &rg, &dests) + } + ) +} diff --git a/web/src/services/gtfs_pull.rs b/web/src/services/gtfs_pull.rs index bfebaea..970a265 100644 --- a/web/src/services/gtfs_pull.rs +++ b/web/src/services/gtfs_pull.rs @@ -71,6 +71,7 @@ pub struct TransitData { // extended lookup methods pub route_id_by_stops: HashMap>, pub stops_by_route_id: HashMap>, + pub platforms_by_route_id: HashMap>, pub stops_by_platform_id: HashMap>, } @@ -96,6 +97,7 @@ impl TransitData { platforms: HashMap::new(), route_id_by_stops: HashMap::new(), stops_by_route_id: HashMap::new(), + platforms_by_route_id: HashMap::new(), stops_by_platform_id: HashMap::new(), calendar_days: HashMap::new(), directions: HashMap::new(), @@ -206,6 +208,16 @@ impl GtfsPullService { .unwrap_or(&HashSet::new()) .clone() } + + pub fn get_platforms_by_route(&self, id: &String) -> HashSet { + let l_state = self.state.lock().unwrap(); + l_state + .transit_data + .platforms_by_route_id + .get(id) + .unwrap_or(&HashSet::new()) + .clone() + } pub fn get_stop_by_id(&self, id: &String) -> Option { let l_state = self.state.lock().unwrap(); @@ -531,6 +543,13 @@ impl GtfsPullService { .entry(global_rt_id.clone()) .or_insert(HashSet::new()) .insert(platform.id.clone()); + + state + .transit_data + .platforms_by_route_id + .entry(global_rt_id.clone()) + .or_insert(HashSet::new()) + .insert(platform.id.clone()); Some(libseptastic::stop_schedule::StopSchedule { arrival_time: i64::from(s.arrival_time?), diff --git a/web/src/templates.rs b/web/src/templates.rs index 5d550c0..3736b39 100644 --- a/web/src/templates.rs +++ b/web/src/templates.rs @@ -13,6 +13,7 @@ use std::{ }; use crate::controllers::stop::StopFilter; +use crate::routing::RouteStep; #[derive(askama::Template)] #[template(path = "layout.html")] @@ -93,6 +94,12 @@ pub struct StopTableTemplate { pub stop_id: String, } +#[derive(askama::Template)] +#[template(path = "routing.html")] +pub struct RoutingTemplate { + pub trips: Vec>, +} + #[derive(askama::Template)] #[template(path = "stop_search_results.html")] pub struct StopSearchResults { diff --git a/web/templates/routing.html b/web/templates/routing.html new file mode 100644 index 0000000..e199e3e --- /dev/null +++ b/web/templates/routing.html @@ -0,0 +1,16 @@ +{% for trip in trips %} +
+ {% for step in trip %} + {% match step %} + {% when RouteStep::Walk(walk) %} +

Walk {{ walk.dist }}m to {{ walk.dest }}

+ {% when RouteStep::Transit(transit) %} +

Ride {{transit.route}} to {{ transit.platform }}

+ {% when RouteStep::Origin %} +

begin at origin

+ {% when RouteStep::Destination %} +

arrive at destination

+ {% endmatch %} + {% endfor %} +
+{% endfor %}