baby steps for routing
This commit is contained in:
parent
5998a56e98
commit
9d0586288d
7 changed files with 152 additions and 41 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1 +1,4 @@
|
|||
.env
|
||||
tracking_data*
|
||||
target/
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))?
|
||||
|
|
|
|||
|
|
@ -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<String, libseptastic::stop::Stop>
|
||||
pub fn get_platforms_near(cds: Coordinates,
|
||||
all_platforms: &HashMap<String, Arc<libseptastic::stop::Platform>>
|
||||
) -> HashSet<String> {
|
||||
let near_thresh_km = 0.45;
|
||||
let mut stops: HashSet<String> = HashSet::new();
|
||||
let mut platforms: HashSet<String> = 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<String, libseptastic::stop::Stop>,
|
||||
all_platforms: &HashMap<String, Arc<libseptastic::stop::Platform>>,
|
||||
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::<String, BTreeSet<RoutingNodePointer>>::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<String>, mut visited: HashSet<String>, max_legs: u8) -> Option<String> {
|
||||
|
||||
#[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<String>, mut visited: HashSet<String>, max_legs: u8) -> Option<Vec<RouteStep>> {
|
||||
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>) -> String {
|
||||
let mut resp = String::new();
|
||||
pub fn bfs_rts(origin: &String, graph: &RoutingGraph, dests: &HashSet<String>) -> Vec<Vec<RouteStep>> {
|
||||
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<String>) -
|
|||
|
||||
resp
|
||||
}
|
||||
|
||||
#[actix_web::get("/routing")]
|
||||
pub async fn get_routing_sample(
|
||||
state: Data<Arc<AppState>>,
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ pub struct TransitData {
|
|||
// extended lookup methods
|
||||
pub route_id_by_stops: HashMap<String, HashSet<String>>,
|
||||
pub stops_by_route_id: HashMap<String, HashSet<String>>,
|
||||
pub platforms_by_route_id: HashMap<String, HashSet<String>>,
|
||||
pub stops_by_platform_id: HashMap<String, Arc<libseptastic::stop::Stop>>,
|
||||
}
|
||||
|
||||
|
|
@ -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<String> {
|
||||
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<libseptastic::stop::Stop> {
|
||||
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?),
|
||||
|
|
|
|||
|
|
@ -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<Vec<RouteStep>>,
|
||||
}
|
||||
|
||||
#[derive(askama::Template)]
|
||||
#[template(path = "stop_search_results.html")]
|
||||
pub struct StopSearchResults {
|
||||
|
|
|
|||
16
web/templates/routing.html
Normal file
16
web/templates/routing.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{% for trip in trips %}
|
||||
<div style="border-width: 1px; border-color: black; border-style: dotted; margin: 10px;">
|
||||
{% for step in trip %}
|
||||
{% match step %}
|
||||
{% when RouteStep::Walk(walk) %}
|
||||
<p>Walk {{ walk.dist }}m to {{ walk.dest }}</p>
|
||||
{% when RouteStep::Transit(transit) %}
|
||||
<p>Ride {{transit.route}} to {{ transit.platform }}</p>
|
||||
{% when RouteStep::Origin %}
|
||||
<p>begin at origin</p>
|
||||
{% when RouteStep::Destination %}
|
||||
<p>arrive at destination</p>
|
||||
{% endmatch %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue