minimal routing code

This commit is contained in:
Nicholas Orlowsky 2025-11-19 18:32:06 -05:00
parent 2d8f131b91
commit a7d323056a
No known key found for this signature in database
GPG key ID: A9F3BA4C0AA7A70B
6 changed files with 151 additions and 20 deletions

View file

@ -1,6 +1,7 @@
use actix_web::{get, web::{self, Data}, HttpRequest, HttpResponse, Responder};
use anyhow::anyhow;
use std::{collections::HashSet, sync::Arc, time::Instant};
use log::info;
use std::{collections::{HashMap, HashSet}, sync::Arc, time::Instant};
use libseptastic::{direction, route::RouteType, stop_schedule::Trip};
use serde::{Serialize, Deserialize};
@ -35,6 +36,7 @@ async fn get_routes_html(req: HttpRequest, state: Data<Arc<AppState>>) -> impl R
}).await
}
#[get("/routes.json")]
async fn get_routes_json(state: Data<Arc<AppState>>) -> impl Responder {
let all_routes: Vec<libseptastic::route::Route> = state.gtfs_service.get_routes();
@ -73,6 +75,106 @@ async fn get_route_info(route_id: String, state: Data<Arc<AppState>>) -> ::anyho
schedule: trips
})
}
pub fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
let r = 6371.0; // Earth's radius in kilometers
let d_lat = (lat2 - lat1).to_radians();
let d_lon = (lon2 - lon1).to_radians();
let lat1_rad = lat1.to_radians();
let lat2_rad = lat2.to_radians();
let a = (d_lat / 2.0).sin().powi(2)
+ lat1_rad.cos() * lat2_rad.cos() * (d_lon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().asin();
r * c
}
enum RoutingNodeType {
Origin,
Destination,
Midpoint
}
struct RoutingNodePointer {
pub stop_id: String,
pub route_id: String,
pub stop_sequence: u64,
pub direction: u64
}
struct RoutingNode {
pub node_type: RoutingNodeType,
pub stop_id: String,
pub next_stops_per_routes: HashMap<String, HashSet<RoutingNodePointer>>,
pub visited: bool
}
struct TripState {
pub used_lines: HashSet<String>
}
struct Coordinates {
pub lat: f64,
pub lng: f64,
}
#[get("/directions")]
async fn get_directions(state: Data<Arc<AppState>>) -> impl Responder {
let near_thresh = 0.45;
let sig_cds = Coordinates {
lat: 40.008420,
lng: -75.213439
};
let home_cds = Coordinates {
lat: 39.957210,
lng: -75.166214
};
let all_stops = state.gtfs_service.get_all_stops();
let mut response = String::new();
let mut origin_stops: HashSet<String> = HashSet::new();
let mut dest_stops: HashSet<String> = HashSet::new();
let mut graph = HashMap::<String, RoutingNode>::new();
for stop_p in &all_stops {
let stop = stop_p.1;
let dist = haversine_distance(sig_cds.lat, sig_cds.lng, stop.lat, stop.lng);
if dist.abs() < near_thresh {
origin_stops.insert(stop.id.clone());
graph.insert(stop.id.clone(), RoutingNode {
node_type: RoutingNodeType::Origin,
stop_id: stop.id.clone(),
next_stops_per_routes: ,
visited: false
});
}
}
for stop_p in &all_stops {
let stop = stop_p.1;
let dist = haversine_distance(home_cds.lat, home_cds.lng, stop.lat, stop.lng);
if dist.abs() < near_thresh {
dest_stops.insert(stop.id.clone());
}
}
return response;
}
#[get("/route/{route_id}")]
async fn get_route(state: Data<Arc<AppState>>, req: HttpRequest, info: web::Query<RouteQueryParams>, path: web::Path<String>) -> impl Responder {

View file

@ -105,6 +105,7 @@ async fn main() -> ::anyhow::Result<()> {
.service(controllers::route::get_route)
.service(controllers::route::get_routes_json)
.service(controllers::route::get_routes_html)
.service(controllers::route::get_directions)
.service(get_index)
.service(actix_files::Files::new("/assets", "./assets"))
})

View file

@ -1,8 +1,6 @@
use std::{collections::HashMap, env, hash::Hash, io::Cursor, path::PathBuf, sync::{Arc, Mutex}, thread, time::Duration};
use std::{collections::{HashMap, HashSet}, env, hash::Hash, io::Cursor, path::PathBuf, sync::{Arc, Mutex}, thread, time::Duration};
use anyhow::anyhow;
use gtfs_structures::Trip;
use libseptastic::agency;
use log::{info, error};
use serde::{Deserialize, Serialize};
use zip::ZipArchive;
@ -27,7 +25,9 @@ struct GtfsFile {
struct TransitData {
pub routes: HashMap<String, libseptastic::route::Route>,
pub agencies: HashMap<String, libseptastic::agency::Agency>,
pub trips: HashMap<String, Vec<libseptastic::stop_schedule::Trip>>
pub trips: HashMap<String, Vec<libseptastic::stop_schedule::Trip>>,
pub stops: HashMap<String, libseptastic::stop::Stop>,
pub route_id_by_stops: HashMap<String, HashSet<String>>
}
struct GtfsPullServiceState {
@ -43,7 +43,7 @@ pub struct GtfsPullService {
impl TransitData {
pub fn new() -> Self {
return TransitData { routes: HashMap::new(), agencies: HashMap::new(), trips: HashMap::new() }
return TransitData { routes: HashMap::new(), agencies: HashMap::new(), trips: HashMap::new(), stops: HashMap::new(), route_id_by_stops: HashMap::new() }
}
}
@ -102,6 +102,26 @@ impl GtfsPullService {
}
}
pub fn get_all_routes(&self) -> HashMap<String, libseptastic::route::Route> {
let l_state = self.state.lock().unwrap();
l_state.transit_data.routes.clone()
}
pub fn get_all_stops(&self) -> HashMap<String, libseptastic::stop::Stop> {
let l_state = self.state.lock().unwrap();
l_state.transit_data.stops.clone()
}
pub fn get_all_trips(&self) -> HashMap<String, Vec<libseptastic::stop_schedule::Trip>> {
let l_state = self.state.lock().unwrap();
l_state.transit_data.trips.clone()
}
pub fn get_routes_at_stop(&self, id: String) -> HashSet<String> {
let l_state = self.state.lock().unwrap();
l_state.transit_data.route_id_by_stops.get(&id).unwrap_or(&HashSet::new()).clone()
}
pub fn get_schedule(&self, route_id: String) -> anyhow::Result<Vec<libseptastic::stop_schedule::Trip>> {
let l_state = self.state.lock().unwrap();
if let Some(trips) = l_state.transit_data.trips.get(&route_id) {
@ -189,12 +209,25 @@ impl GtfsPullService {
});
}
for stop in &gtfs.stops {
l_state.transit_data.stops.insert(stop.1.id.clone(), libseptastic::stop::Stop {
id: stop.1.id.clone(),
name: stop.1.name.clone().unwrap(),
lat: stop.1.latitude.unwrap(),
lng: stop.1.longitude.unwrap(),
stop_type: libseptastic::stop::StopType::Normal
});
}
for trip in &gtfs.trips {
let global_rt_id = match &hack_agency {
Some(a) => format!("{}_{}", a.id, trip.1.route_id.clone()),
None => format!("{}", trip.1.route_id.clone())
};
let sched = trip.1.stop_times.iter().map(|s| libseptastic::stop_schedule::StopSchedule{
let sched = trip.1.stop_times.iter().map(|s| {
l_state.transit_data.route_id_by_stops.entry(s.stop.id.clone()).or_insert(HashSet::new()).insert(trip.1.route_id.clone());
libseptastic::stop_schedule::StopSchedule{
arrival_time: i64::from(s.arrival_time.unwrap()),
stop_sequence: i64::from(s.stop_sequence),
stop: libseptastic::stop::Stop {
@ -203,7 +236,7 @@ impl GtfsPullService {
lng: s.stop.longitude.unwrap(),
id: s.stop.id.parse().unwrap(),
stop_type: libseptastic::stop::StopType::Normal
}
}}
}).collect();
let trip = libseptastic::stop_schedule::Trip{

View file

@ -1,10 +1,9 @@
use chrono::Utc;
use serde_json::Value;
use serde::de;
use sqlx::{Execute, Postgres, QueryBuilder, Transaction};
use sqlx::{Postgres, QueryBuilder, Transaction};
use std::sync::{Arc};
use futures::lock::Mutex;
use std::thread;
use std::collections::HashMap;
use std::time::Duration;
use log::{error, info};
@ -96,7 +95,6 @@ impl TripTrackingService {
}
let query = query_builder.build();
//info!("{}", query.sql());
query.execute(&mut **transaction).await?;
Ok(())
@ -117,7 +115,7 @@ impl TripTrackingService {
}
pub fn start(&self) {
info!("Starting live service");
info!("Starting live tracking service");
let cloned_state = Arc::clone(&self.state);
tokio::spawn( async move {
loop {
@ -200,11 +198,8 @@ impl TripTrackingService {
);
}
info!("Logged live data");
let mut svc = service.lock().await;
info!("Logged live data");
let mut tx = svc.database.begin().await?;
Self::log_delay(&mut tx, &new_map, Utc::now().timestamp_nanos_opt().unwrap()).await?;
tx.commit().await?;

View file

@ -39,7 +39,7 @@ pub struct IndexTemplate {
#[derive(Debug, Serialize)]
pub struct TimetableStopRow {
pub stop_id: i64,
pub stop_id: i64,
pub stop_name: String,
pub stop_sequence: i64,
pub times: Vec<Option<i64>>
@ -102,12 +102,12 @@ pub fn build_timetables(
.collect();
let mut stop_map: BTreeMap<i64, (i64, String, Vec<Option<i64>>)> = BTreeMap::new();
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)
.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
@ -120,7 +120,7 @@ pub fn build_timetables(
let mut rows: Vec<TimetableStopRow> = stop_map
.into_iter()
.map(|(stop_id, (stop_sequence, stop_name, times))| TimetableStopRow {
stop_id,
stop_id: stop_id.parse().unwrap(),
stop_sequence,
stop_name,
times,

View file

@ -10,7 +10,7 @@ pub enum StopType {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Stop {
pub id: i64,
pub id: String,
pub name: String,
pub lat: f64,
pub lng: f64,