From 458f71530fb792f5870815a1b80914e1ccddeac5 Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Mon, 16 Feb 2026 22:34:38 -0500 Subject: [PATCH] search stops --- README.md | 42 +++++++++++++++++++++++++- web/src/controllers/stop.rs | 38 +++++++++++++++++++++-- web/src/main.rs | 3 +- web/src/services/gtfs_pull.rs | 1 - web/src/templates.rs | 7 +++++ web/templates/index.html | 14 ++++++--- web/templates/stop_search_results.html | 12 ++++++++ web/templates/stops.html | 13 ++++++++ 8 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 web/templates/stop_search_results.html diff --git a/README.md b/README.md index f9d4011..1b20de3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,47 @@ live information for gtfs data feeds (though is specifically tailored to SEPTA). - [] implement support for connecting legs of a Regional Rail trip - [] work on providing a better experience with non-SEPTA gtfs files - [] simple account system to save info such as work commute/fav routes +- [] parse gtfs data ourselves instead of using library +- [] support more of the gtfs specification + +## Building + +The build system for this isn't extremely straightforward. I have a shell.nix that +contains some shell utils specific to this project, but don't have a flake setup +that would allow you to easily build. If you have a newer rust toolchain installed, +you should be able to run this with Cargo. + +I also provide a Dockerfile you can use as the base for a dev container. + +## Running + +There's a config file in this repository that has a format for specifying GTFS +file locations as well as 'annotations' to those files to enrich them. Currently, +the following annotations exist: + +- **multiplatform_stops:** used to combine multiple stops into one stop as though +each 'sub-stop' is it's own platform. This is useful for when a transit authority +(specifically one in southeastern PA) does not provide [stop_areas.txt](https://gtfs.org/documentation/schedule/reference/#stop_areastxt). + +```yaml +- id: 'WTC' + name: 'Wissahickon Transit Center' + platform_station_ids: + - 'SEPTABUS_2' + - 'SEPTABUS_31032' + - 'SEPTABUS_32980' + - 'SEPTABUS_32988' + - 'SEPTABUS_32989' + - 'SEPTABUS_32990' + - 'SEPTABUS_32992' + - 'SEPTABUS_32993' + - 'SEPTARAIL_90220' +``` + +This project uses postgres (via sqlx) to store an archive of realtime data, though +it is not required to run SEPTASTIC (all transit data is stored in-memory). --- -I have an instance of this running at [septastic.net](https://septastic.net) +I have an instance of this running at [septastic.net](https://septastic.net), +feel free to check it out. diff --git a/web/src/controllers/stop.rs b/web/src/controllers/stop.rs index 1cd8012..5321509 100644 --- a/web/src/controllers/stop.rs +++ b/web/src/controllers/stop.rs @@ -4,13 +4,12 @@ use crate::{ templates::TripPerspective, }; use actix_web::{ - HttpResponse, Responder, get, - web::{self, Data}, + HttpResponse, Responder, get, post, web::{self, Data} }; use askama::Template; use chrono::{TimeDelta, Timelike}; use chrono_tz::America::New_York; -use libseptastic::stop_schedule::{SeatAvailability, Trip, TripTracking}; +use libseptastic::{stop::Stop, stop_schedule::{SeatAvailability, Trip, TripTracking}}; use serde::{Deserialize, Serialize}; use serde_qs::actix::QsQuery; use std::{ @@ -193,6 +192,39 @@ async fn get_stops_html(state: Data>, resp: SessionResponse) -> im ) } +#[derive(Deserialize)] +struct StringSearch { + search: String +} + +#[post("/stops/search")] +async fn search_stops_html(state: Data>, params: web::Form) -> impl Responder { + let results_limit = 25; + let search_str = params.search.to_lowercase(); + let stops: Vec = state + .gtfs_service + .get_all_stops() + .iter() + .filter_map(|f| { + // Non-ideal + if f.1.name.to_lowercase().contains(&search_str) || + f.1.id.to_lowercase().contains(&search_str) { + Some(libseptastic::stop::Stop::clone(f.1)) + } else { + None + } + }) + .collect(); + + HttpResponse::Ok().body(crate::templates::StopSearchResults { + results: if stops.len() > results_limit { + stops[..results_limit].to_vec() + } else { + stops + } + }.render().unwrap()) +} + #[get("/stop/{stop_id}/table")] async fn get_stop_table_html( state: Data>, diff --git a/web/src/main.rs b/web/src/main.rs index e929c0c..fe13eb5 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -17,7 +17,7 @@ pub struct AppState { #[tokio::main] async fn main() -> ::anyhow::Result<()> { - env_logger::init_from_env(Env::default().default_filter_or("septastic_api=info")); + env_logger::init_from_env(Env::default().default_filter_or("septastic_web=info")); dotenv().ok(); let version: &str = option_env!("CARGO_PKG_VERSION").expect("Expected package version"); @@ -54,6 +54,7 @@ async fn main() -> ::anyhow::Result<()> { .service(controllers::route::get_routes_json) .service(controllers::stop::get_stops_html) .service(controllers::stop::get_stop_html) + .service(controllers::stop::search_stops_html) .service(controllers::stop::get_stop_table_html) .service(controllers::index::get_index_html) .service(actix_files::Files::new("/assets", "./assets")) diff --git a/web/src/services/gtfs_pull.rs b/web/src/services/gtfs_pull.rs index 4ea5e8f..3056706 100644 --- a/web/src/services/gtfs_pull.rs +++ b/web/src/services/gtfs_pull.rs @@ -325,7 +325,6 @@ impl GtfsPullService { ) -> anyhow::Result<()> { for route in >fs.routes { let global_rt_id = make_global_id!(prefix, route.1.id); - info!("{}", global_rt_id); let rt_name = match route.1.long_name.clone() { Some(x) => x, diff --git a/web/src/templates.rs b/web/src/templates.rs index 3d1caae..a0d73a9 100644 --- a/web/src/templates.rs +++ b/web/src/templates.rs @@ -1,5 +1,6 @@ use chrono::Timelike; use chrono_tz::America::New_York; +use libseptastic::stop::Stop; use libseptastic::stop_schedule::TripTracking::Tracked; use libseptastic::{ direction::Direction, @@ -92,6 +93,12 @@ pub struct StopTableTemplate { pub stop_id: String, } +#[derive(askama::Template)] +#[template(path = "stop_search_results.html")] +pub struct StopSearchResults { + pub results: Vec +} + pub fn build_timetables(directions: Vec, trips: Vec) -> Vec { let mut results = Vec::new(); diff --git a/web/templates/index.html b/web/templates/index.html index 5c40bdf..96351ee 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -3,11 +3,15 @@ A fantastic way to ride SEPTA

- SEPTASTIC is a website and (a soon to be) mobile app. Its purpose is to provide - information about how to ride SEPTA (and connecting transit authorities) in a - quick and information-rich manner. + SEPTASTIC is a website which provides information about riding SEPTA. It's + source code is available at + git.nickorlow.com

+

- Currently, all this website has is timetables for every - SEPTA route. More to come soon! + This website is mostly for personal use, and thus the interface and data + is tailored to my SEPTA riding experience. This manifests in a couple of + weird things, such as the non-existent 'Susquehanna Transit Center'.

+ + diff --git a/web/templates/stop_search_results.html b/web/templates/stop_search_results.html new file mode 100644 index 0000000..9c16d43 --- /dev/null +++ b/web/templates/stop_search_results.html @@ -0,0 +1,12 @@ +{% for stop in results %} + + +

]

+
+{% endfor %} + +{% if results.len() == 0 %} +

No results found

+{% endif %} diff --git a/web/templates/stops.html b/web/templates/stops.html index b64ba08..17158d6 100644 --- a/web/templates/stops.html +++ b/web/templates/stops.html @@ -14,6 +14,19 @@ {% endfor %} +
+ +

Other Stops

+
+

SEPTA has 13,000+ stops, search for yours here

+ +
+
+