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
|
.env
|
||||||
|
tracking_data*
|
||||||
|
target/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ use actix_web::{
|
||||||
HttpResponse, Responder, get,
|
HttpResponse, Responder, get,
|
||||||
web::{self, Data},
|
web::{self, Data},
|
||||||
};
|
};
|
||||||
use libseptastic::{route::RouteType, stop_schedule::Trip};
|
use libseptastic::{route::RouteType, stop::StopType, stop_schedule::Trip};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::{HashMap, HashSet}, io::Write, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
struct RouteQueryParams {
|
struct RouteQueryParams {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ use log::*;
|
||||||
use services::gtfs_pull;
|
use services::gtfs_pull;
|
||||||
use std::{fs::File, io::Read, sync::Arc};
|
use std::{fs::File, io::Read, sync::Arc};
|
||||||
|
|
||||||
|
use crate::routing::{bfs_rts, construct_graph};
|
||||||
|
|
||||||
mod controllers;
|
mod controllers;
|
||||||
mod services;
|
mod services;
|
||||||
mod session_middleware;
|
mod session_middleware;
|
||||||
mod templates;
|
mod templates;
|
||||||
|
mod routing;
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
gtfs_service: services::gtfs_pull::GtfsPullService,
|
gtfs_service: services::gtfs_pull::GtfsPullService,
|
||||||
|
|
@ -58,6 +61,7 @@ async fn main() -> ::anyhow::Result<()> {
|
||||||
.service(controllers::stop::get_stop_table_html)
|
.service(controllers::stop::get_stop_table_html)
|
||||||
.service(controllers::index::get_index_html)
|
.service(controllers::index::get_index_html)
|
||||||
.service(controllers::map::get_map_element_html)
|
.service(controllers::map::get_map_element_html)
|
||||||
|
.service(routing::get_routing_sample)
|
||||||
.service(actix_files::Files::new("/assets", "./assets"))
|
.service(actix_files::Files::new("/assets", "./assets"))
|
||||||
})
|
})
|
||||||
.bind(("0.0.0.0", 8080))?
|
.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 log::info;
|
||||||
|
|
||||||
use crate::services;
|
use crate::{AppState, services, session_middleware::{SessionResponder, SessionResponse}, templates::RoutingTemplate};
|
||||||
|
|
||||||
pub struct RoutingNodePointer {
|
pub struct RoutingNodePointer {
|
||||||
pub stop_id: String,
|
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,
|
pub fn get_platforms_near(cds: Coordinates,
|
||||||
all_stops: &HashMap<String, libseptastic::stop::Stop>
|
all_platforms: &HashMap<String, Arc<libseptastic::stop::Platform>>
|
||||||
) -> HashSet<String> {
|
) -> HashSet<String> {
|
||||||
let near_thresh_km = 0.45;
|
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 {
|
for platform_p in all_platforms {
|
||||||
let stop = stop_p.1;
|
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 {
|
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() {
|
pub fn get_stop_as_node() {
|
||||||
|
|
@ -103,17 +106,17 @@ pub fn get_stop_as_node() {
|
||||||
|
|
||||||
pub fn construct_graph(
|
pub fn construct_graph(
|
||||||
dest: Coordinates,
|
dest: Coordinates,
|
||||||
all_stops: &HashMap<String, libseptastic::stop::Stop>,
|
all_platforms: &HashMap<String, Arc<libseptastic::stop::Platform>>,
|
||||||
gtfs_service: &services::gtfs_pull::GtfsPullService
|
gtfs_service: &services::gtfs_pull::GtfsPullService
|
||||||
) -> RoutingGraph {
|
) -> RoutingGraph {
|
||||||
let mut graph = RoutingGraph::new();
|
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 {
|
for platform_p in all_platforms {
|
||||||
let stop = stop_p.1;
|
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 cont = {
|
||||||
let mut ret = false;
|
let mut ret = false;
|
||||||
|
|
@ -130,24 +133,24 @@ pub fn construct_graph(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
graph.insert(stop.id.clone(), RoutingNode {
|
graph.insert(platform.id.clone(), RoutingNode {
|
||||||
stop_id: stop.id.clone(),
|
stop_id: platform.id.clone(),
|
||||||
stop_name: stop.name.clone(),
|
stop_name: platform.name.clone(),
|
||||||
next_stops_per_routes: {
|
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();
|
let mut other_stops = HashMap::<String, BTreeSet<RoutingNodePointer>>::new();
|
||||||
|
|
||||||
for route in &routes {
|
for route in &routes {
|
||||||
let mut stops = gtfs_service.get_stops_by_route(&route);
|
let mut platforms = gtfs_service.get_platforms_by_route(&route);
|
||||||
stops.remove(&stop.id);
|
platforms.remove(&platform.id);
|
||||||
let rnps = {
|
let rnps = {
|
||||||
let mut ret = BTreeSet::new();
|
let mut ret = BTreeSet::new();
|
||||||
for stop in &stops {
|
for platform in &platforms {
|
||||||
let stp = all_stops.get(stop).unwrap();
|
let plt = all_platforms.get(platform).unwrap();
|
||||||
ret.insert(RoutingNodePointer{
|
ret.insert(RoutingNodePointer{
|
||||||
dest_dist: haversine_distance(dest.lat, dest.lng, stp.lat, stp.lng),
|
dest_dist: haversine_distance(dest.lat, dest.lng, plt.lat, plt.lng),
|
||||||
stop_id: stop.clone(),
|
stop_id: platform.clone(),
|
||||||
route_id: route.clone(),
|
route_id: route.clone(),
|
||||||
stop_sequence: 0,
|
stop_sequence: 0,
|
||||||
direction: 0
|
direction: 0
|
||||||
|
|
@ -167,23 +170,50 @@ pub fn construct_graph(
|
||||||
|
|
||||||
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 {
|
if max_legs == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut limited_rts = HashSet::new();
|
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);
|
limited_rts.insert(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !limited_rts.contains(&route_id.as_str()) {
|
if !limited_rts.contains(&route_id.as_str()) {
|
||||||
return None;
|
info!("{} is not in the allowlist", route_id.as_str());
|
||||||
}
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("made it");
|
||||||
|
|
||||||
if let Some(origin_stop) = graph.get(origin) {
|
if let Some(origin_stop) = graph.get(origin) {
|
||||||
if dests.contains(origin) {
|
if dests.contains(origin) {
|
||||||
return Some(format!("[stop {} via rt {}] --> DEST", origin_stop.stop_name, route_id))
|
return Some(vec![ RouteStep::Transit(TransitRouteStep { platform: origin_stop.stop_name.clone(), route: route_id.clone() }) , RouteStep::Destination])
|
||||||
}
|
}
|
||||||
|
|
||||||
if visited.contains(origin) {
|
if visited.contains(origin) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -196,8 +226,9 @@ pub fn bfs_rts_int(route_id: &String, origin: &String, graph: &RoutingGraph, des
|
||||||
}
|
}
|
||||||
|
|
||||||
for rnp in items.1 {
|
for rnp in items.1 {
|
||||||
if let Some(rt) = bfs_rts_int(items.0, &rnp.stop_id, graph, dests, visited.clone(), max_legs - 1) {
|
if let Some(mut rt) = bfs_rts_int(items.0, &rnp.stop_id, graph, dests, visited.clone(), max_legs - 1).clone() {
|
||||||
return Some(format!("[stop {} via rt {}] >>[XFER]>> {}", origin_stop.stop_name, route_id, rt))
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bfs_rts(origin: &String, graph: &RoutingGraph, dests: &HashSet<String>) -> String {
|
pub fn bfs_rts(origin: &String, graph: &RoutingGraph, dests: &HashSet<String>) -> Vec<Vec<RouteStep>> {
|
||||||
let mut resp = String::new();
|
let mut resp = vec![];
|
||||||
|
|
||||||
if let Some(origin_stop) = graph.get(origin) {
|
if let Some(origin_stop) = graph.get(origin) {
|
||||||
for items in &origin_stop.next_stops_per_routes {
|
for items in &origin_stop.next_stops_per_routes {
|
||||||
let route_id = items.0;
|
let route_id = items.0;
|
||||||
|
|
||||||
for rnp in items.1 {
|
for rnp in items.1 {
|
||||||
if let Some(rt) = bfs_rts_int(route_id, &rnp.stop_id, graph, dests, HashSet::new(), 3) {
|
if let Some(mut rt) = bfs_rts_int(route_id, &rnp.stop_id, graph, dests, HashSet::new(), 3).clone() {
|
||||||
resp += format!("ORIGIN --> [stop {} via rt {}] --> {}\n", origin_stop.stop_name, route_id, rt).as_str();
|
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
|
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
|
// extended lookup methods
|
||||||
pub route_id_by_stops: HashMap<String, HashSet<String>>,
|
pub route_id_by_stops: HashMap<String, HashSet<String>>,
|
||||||
pub stops_by_route_id: 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>>,
|
pub stops_by_platform_id: HashMap<String, Arc<libseptastic::stop::Stop>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,6 +97,7 @@ impl TransitData {
|
||||||
platforms: HashMap::new(),
|
platforms: HashMap::new(),
|
||||||
route_id_by_stops: HashMap::new(),
|
route_id_by_stops: HashMap::new(),
|
||||||
stops_by_route_id: HashMap::new(),
|
stops_by_route_id: HashMap::new(),
|
||||||
|
platforms_by_route_id: HashMap::new(),
|
||||||
stops_by_platform_id: HashMap::new(),
|
stops_by_platform_id: HashMap::new(),
|
||||||
calendar_days: HashMap::new(),
|
calendar_days: HashMap::new(),
|
||||||
directions: HashMap::new(),
|
directions: HashMap::new(),
|
||||||
|
|
@ -207,6 +209,16 @@ impl GtfsPullService {
|
||||||
.clone()
|
.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> {
|
pub fn get_stop_by_id(&self, id: &String) -> Option<libseptastic::stop::Stop> {
|
||||||
let l_state = self.state.lock().unwrap();
|
let l_state = self.state.lock().unwrap();
|
||||||
match l_state.transit_data.stops.get(id) {
|
match l_state.transit_data.stops.get(id) {
|
||||||
|
|
@ -532,6 +544,13 @@ impl GtfsPullService {
|
||||||
.or_insert(HashSet::new())
|
.or_insert(HashSet::new())
|
||||||
.insert(platform.id.clone());
|
.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 {
|
Some(libseptastic::stop_schedule::StopSchedule {
|
||||||
arrival_time: i64::from(s.arrival_time?),
|
arrival_time: i64::from(s.arrival_time?),
|
||||||
stop_sequence: i64::from(s.stop_sequence),
|
stop_sequence: i64::from(s.stop_sequence),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::controllers::stop::StopFilter;
|
use crate::controllers::stop::StopFilter;
|
||||||
|
use crate::routing::RouteStep;
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
#[template(path = "layout.html")]
|
#[template(path = "layout.html")]
|
||||||
|
|
@ -93,6 +94,12 @@ pub struct StopTableTemplate {
|
||||||
pub stop_id: String,
|
pub stop_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(askama::Template)]
|
||||||
|
#[template(path = "routing.html")]
|
||||||
|
pub struct RoutingTemplate {
|
||||||
|
pub trips: Vec<Vec<RouteStep>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(askama::Template)]
|
#[derive(askama::Template)]
|
||||||
#[template(path = "stop_search_results.html")]
|
#[template(path = "stop_search_results.html")]
|
||||||
pub struct StopSearchResults {
|
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