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 %}
+
+