From 645d18fc4ea20c851fd2f30c5e31540e26ff6ca6 Mon Sep 17 00:00:00 2001 From: Pagwin Date: Sat, 17 Feb 2024 22:26:33 -0500 Subject: [PATCH] finished the basic api as far as building is concerned but might be buggy --- .gitignore | 2 ++ Cargo.lock | 1 + Cargo.toml | 1 + src/google.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 78 +++++++++++++++++++++++++++++++++++---------- src/path_calc.rs | 70 +++++++++++++++++++++++++++++++++++++++++ src/route.rs | 30 ++++++++++-------- src/stop.rs | 40 ++++++++++++++++++++--- src/types.rs | 9 ++++++ 9 files changed, 279 insertions(+), 34 deletions(-) create mode 100644 src/google.rs create mode 100644 src/path_calc.rs create mode 100644 src/types.rs diff --git a/.gitignore b/.gitignore index 0cf3a6a..824a3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target migration/target +check.sh +GOOGLE_API_KEY diff --git a/Cargo.lock b/Cargo.lock index 076ed3e..c29bd04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,7 @@ name = "bus_api" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "reqwest", "sea-orm", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6b2c076..6829e9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0.79" +chrono = "0.4.34" reqwest = "0.11.24" sea-orm = "0.12.14" serde = { version = "1.0.196", features = ["derive"] } diff --git a/src/google.rs b/src/google.rs new file mode 100644 index 0000000..03dc631 --- /dev/null +++ b/src/google.rs @@ -0,0 +1,82 @@ +use tokio::io::AsyncReadExt; + +pub struct TravelPathInfo{ + pub duration: chrono::Duration, + pub polyline: String +} +pub async fn calc_travel_path(path:&[crate::types::Coords])->anyhow::Result{ + let mut google_api_key = String::new(); + tokio::fs::File::open("GOOGLE_API_KEY").await?.read_to_string(&mut google_api_key).await?; + let client = reqwest::Client::new(); + let response = client.post("https://routes.googleapis.com/directions/v2:computeRoutes") + .body(serde_json::to_string(&build_route_body_pain(path))?) + .header("Content-Type","application/json") + .header("X-Goog-Api-Key:", google_api_key) + .header("X-Goog-FieldMask", "routes.duration,routes.polyline.encodedPolyline,routes.legs") + .send().await?; + let response = serde_json::from_str::(response.text().await?.as_str())?; + + Ok(response.into()) +} +impl From for TravelPathInfo{ + fn from(value: RouteResponse) -> Self { + let RouteResponse { mut routes } = value; + let head = routes.pop().unwrap(); + let duration = chrono::Duration::seconds(head.duration[0..head.duration.len()-1].parse().expect("Expected google's api to return [0-9]s")); + Self{ + polyline: head.polyline.encodedPolyline, + duration + } + } +} +#[derive(serde::Deserialize,serde::Serialize)] +struct RouteResponse{ + routes:Vec +} +#[derive(serde::Deserialize,serde::Serialize)] +struct Route{ + duration: String, + polyline: PolyLine +} +#[derive(serde::Deserialize,serde::Serialize)] +struct PolyLine{ + #[allow(non_snake_case)] + encodedPolyline: String +} +#[derive(serde::Deserialize,serde::Serialize)] +struct Location{ + #[allow(non_snake_case)] + latLng:LocIn +} +#[derive(serde::Deserialize,serde::Serialize)] +struct LocIn{ + latitude: f64, + longitude: f64 +} +fn build_route_body_pain(path:&[crate::types::Coords])->serde_json::Value{ + let time = chrono::offset::Utc::now() + .to_rfc3339_opts(chrono::SecondsFormat::Nanos,true); + let inter = &path[1..path.len()-1]; + let mut val = serde_json::json!({ + "origin":{ + "location": path[0], + "sideOfRoad": true + }, + "destination":{ + "location":path[path.len()-1] + }, + "intermediates": inter, + "travelMode": "DRIVE", + "routingPreference": "TRAFFIC_UNAWARE", + "departureTime": time, + "computeAlternativeRoutes": false, + "routeModifiers": { + "avoidTolls": false, + "avoidHighways": false, + "avoidFerries": false + }, + "languageCode": "en-US", + "units": "IMPERIAL" + }); + val +} diff --git a/src/main.rs b/src/main.rs index 037fb57..121025b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,15 @@ extern crate tokio; extern crate reqwest; extern crate tide; extern crate anyhow; +extern crate chrono; //extern crate sea_orm; mod state; mod route; mod stop; +mod google; +mod types; +mod path_calc; use tide::prelude::*; @@ -38,37 +42,77 @@ async fn main() -> anyhow::Result<()> { /// ] /// } async fn transit_path(mut req: tide::Request)->tide::Result{ + + Ok("".into()) } -/// return the num of estimated seconds for stop ids given in query params +/// return the num of estimated seconds for stop ids given in query params, also need to specify +/// service and route id /// example return: /// 23 async fn transit_estimate(mut req: tide::Request)->tide::Result{ - Ok("".into()) -} - -/// return the coord pairs as json and line color forgiven route id in query params -/// Example return: -/// {"color":"#0000ff", "stops":[[-75,23],[-21,72]]} -async fn bus_route_draw(mut req: tide::Request)->tide::Result{ - match req.query::()?.id.split("_").collect::>()[..] { - ["OCCT", id] => { - let route = route::occt_route_get(id.parse()); + let query = req.query::()?; + let route = match query.service.as_str(){ + "OCCT"=>{ + let routes = route::occt_routes_get().await?; + let Some(route) = routes.into_iter().find(|route| route.id == query.route) else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())}; + route }, - ["BC", id] => { - let route = route::broome_county_route_get(id.parse()); + "BC"=>{ + let routes = route::broome_county_routes_get().await?; + let Some(route) = routes.into_iter().find(|route|route.id == query.route) else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())}; + route }, _ => { - return Ok(tide::Response::builder(400).body("Error: invalid id").into()) + return Ok(tide::Response::builder(400).body("Error: invalid service").into()) + } + }; + let Some((from_idx,_)) = route.stops.iter().enumerate().find(|(_,stop)|stop.id == query.from) + else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())}; + let Some((to_idx,_)) = route.stops.iter().enumerate().find(|(_,stop)|stop.id == query.to) + else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())}; + + Ok(format!("{}", path_calc::bus_travel_time(route, from_idx, to_idx).await?).into()) +} +#[derive(serde::Deserialize)] +struct TransitEstimateQuery{ + service:String, + from:u32, + to:u32, + route: u32 +} +/// return the coord pairs as json and line color forgiven route id in query params +/// Example return: +/// {"color":"#0000ff", "stops":[{"lat":-75.5,"lon":23},...]} +async fn bus_route_draw(mut req: tide::Request)->tide::Result{ + let query = req.query::()?; + let route = match query.service.as_str() { + "OCCT" => { + let routes = route::occt_routes_get().await?; + let Some(route) = routes.into_iter().find(|route| route.id == query.id) else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())}; + route + }, + "BC" => { + let routes = route::broome_county_routes_get().await?; + let Some(route) = routes.into_iter().find(|route|route.id == query.id) else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())}; + route + }, + _ => { + return Ok(tide::Response::builder(400).body("Error: invalid service").into()) } - } - Ok("".into()) + }; + let ret = stop::RouteDraw { + color: route.color, + stops: route.stops.into_iter().map(|stop|{types::Coords{lat:stop.lat,lon:stop.lon}}).collect() + }; + Ok(serde_json::to_string(&ret)?.into()) } #[derive(serde::Deserialize)] struct RouteDrawQuery{ - id: String + id: u32, + service: String } diff --git a/src/path_calc.rs b/src/path_calc.rs new file mode 100644 index 0000000..176ad9b --- /dev/null +++ b/src/path_calc.rs @@ -0,0 +1,70 @@ +pub async fn bus_travel_time(route:crate::route::Route, from_idx:usize, to_idx:usize)->anyhow::Result{ + let coords:Vec = route.stops[from_idx..=to_idx].iter() + .map(|stop|crate::types::Coords{lat:stop.lat, lon:stop.lon}) + .collect(); + let travel_time = crate::google::calc_travel_path(&coords).await?; + Ok(travel_time.duration) +} + +/// Doesn't find the closest walking path just the closest based on euclidean distance +fn sort_routes_by_measureu64+Clone>(measure: F, routes:&mut [crate::route::Route]){ + routes.sort_unstable_by_key(|route|{ + // we don't need the exactly correct order so making everything an integer key is fine + let stop = route.stops.iter().min_by_key(measure.clone()).unwrap(); + + measure(&stop) + }); + +} + + +//const ARB_DIST:u64 = 1_000_000; +const BU: crate::types::Coords = crate::types::Coords{lat:42.08707019482314, lon:-75.96706048564714}; +const BC_BUS_HUB: crate::types::Coords = crate::types::Coords{lat:42.10156942719183, lon:-75.91073683134701}; +// return a list indicating the bus routes to take in order +fn calc_routes(routes:&mut [crate::route::Route], from:crate::types::Coords, to: crate::types::Coords)->Vec{ + if routes.len() == 0 { + return Vec::new() + } + + let measure_distance_to = walk_distance_measure(to); + let measure_distance_from = walk_distance_measure(from); + let measure_distance_bu = walk_distance_measure(BU); + let measure_distance_bc_hub = walk_distance_measure(BC_BUS_HUB); + let route_measure_dist_to = move |route: &crate::route::Route|{ + measure_distance_to(&route.stops.iter().min_by_key(measure_distance_to.clone()).unwrap()) + }; + let route_measure_dist_from = move |route: &crate::route::Route|{ + measure_distance_from(&route.stops.iter().min_by_key(measure_distance_from.clone()).unwrap()) + }; + let route_measure_dist_bu = move |route: &crate::route::Route|{ + measure_distance_bu(&route.stops.iter().min_by_key(measure_distance_bu.clone()).unwrap()) + }; + let route_measure_dist_bc_hub = move |route: &crate::route::Route|{ + measure_distance_bc_hub(&route.stops.iter().min_by_key(measure_distance_bc_hub.clone()).unwrap()) + }; + + let min_direct_route = routes.iter().min_by_key(|route|route_measure_dist_to(route)+route_measure_dist_from(route)).unwrap(); + let min_to_bu_route = routes.iter().min_by_key(|route|route_measure_dist_from(route)+route_measure_dist_bu(route)).unwrap(); + let min_from_bu_route = routes.iter().min_by_key(|route|route_measure_dist_bu(route)+route_measure_dist_to(route)).unwrap(); + let min_to_bc_hub = routes.iter().min_by_key(|route|route_measure_dist_from(route)+route_measure_dist_bc_hub(route)).unwrap(); + let min_from_bc_hub = routes.iter().min_by_key(|route|route_measure_dist_bc_hub(route)+route_measure_dist_to(route)).unwrap(); + + let direct_route_cost = route_measure_dist_to(min_direct_route) + route_measure_dist_from(min_direct_route); + let bu_route_cost = route_measure_dist_from(min_to_bu_route) + route_measure_dist_to(min_from_bu_route); + let bc_route_cost = route_measure_dist_from(min_to_bc_hub) + route_measure_dist_from(min_from_bc_hub); + + select_min(vec![(direct_route_cost,vec![min_direct_route.clone()]), (bu_route_cost,vec![min_to_bu_route.clone(),min_from_bu_route.clone()]), (bc_route_cost, vec![min_to_bc_hub.clone(), min_from_bc_hub.clone()])]) +} + +fn walk_distance_measure(pos: crate::types::Coords)->impl Fn(&&crate::stop::Stop)->u64+Clone{ + + move |stop:&&crate::stop::Stop|{ + (((stop.lat-pos.lat).powi(2) + (stop.lon - pos.lon).powi(2)).powf(0.5)*1_000_000.0) as u64 + } +} + +fn select_min(dist_route_pairs:Vec<(u64,Vec)>)->Vec{ + let (_, ret) = dist_route_pairs.into_iter().min_by_key(|(dist,_)|dist.clone()).unwrap(); + ret +} diff --git a/src/route.rs b/src/route.rs index decb19a..9c18462 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,11 +1,13 @@ -struct Route { - id:u32, - name: String, - short_name: String, - color: String, - poly_line: String, - stops: Vec +#[derive(Hash, Clone)] +pub struct Route { + pub id:u32, + pub name: String, + pub short_name: String, + pub color: String, + pub poly_line: String, + pub stops: Vec, + pub service: crate::types::Service } #[derive(serde::Deserialize)] struct OcctRoute { @@ -44,7 +46,7 @@ struct BCRoute{ struct OcctWrap{ get_routes:Vec } -async fn occt_routes_get()->anyhow::Result>{ +pub async fn occt_routes_get()->anyhow::Result>{ let resp = reqwest::get("http://binghamtonupublic.etaspot.net/service.php?service=get_routes&token=TESTING").await?; let body = resp.text().await?; let OcctWrap{get_routes: routes} = serde_json::from_str(body.as_str())?; @@ -61,30 +63,32 @@ async fn occt_routes_get()->anyhow::Result>{ name:route.name, poly_line:route.poly_line, short_name: route.short_name, - stops + stops, + service: crate::types::Service::OCCT } }).collect(); Ok(routes) } -async fn broom_county_routes_get()->anyhow::Result{ +pub async fn broome_county_routes_get()->anyhow::Result>{ let resp = reqwest::get("https://bctransit.doublemap.com/map/v2/routes").await?; let body = resp.text().await?; let routes:Vec = serde_json::from_str(body.as_str())?; let stops = crate::stop::BC_stops_get().await?; let routes = routes.into_iter().map(|route|{ - let stops = stops.iter() + let stops:Vec = stops.iter() .filter(|stop|{ route.stops.iter().any(|id|*id==stop.id) }).map(Clone::clone).collect(); - let poly_line = crate::stop::poly_encode_stops(stops).await; + let poly_line = crate::stop::poly_encode_stops(stops.clone()); Route{ id:route.id, color:route.color, name:route.name, poly_line, short_name: route.short_name, - stops + stops, + service: crate::types::Service::BC } }).collect(); Ok(routes) diff --git a/src/stop.rs b/src/stop.rs index de83dea..309c0a5 100644 --- a/src/stop.rs +++ b/src/stop.rs @@ -1,10 +1,20 @@ -#[derive(Clone)] +#[derive(Clone, PartialEq, PartialOrd)] pub struct Stop{ pub id:u32, pub name:String, pub lat: f64, pub lon: f64, } +impl std::hash::Hash for Stop { + fn hash(&self, state: &mut H) { + state.write_u32(self.id); + } +} +impl From for crate::types::Coords { + fn from(value: Stop) -> Self { + crate::types::Coords {lat: value.lat, lon: value.lon} + } +} #[derive(serde::Deserialize)] struct OcctStop{ @@ -53,12 +63,13 @@ pub fn poly_encode_stops>(stops:I)->String{ .map(|stop|{ enc_float(stop.lat) }) - .reduce(|s1,s2|s1+&s2) + .reduce(|s1,s2|s1+&s2).unwrap_or("".into()) +} fn enc_float(num:f64)->String{ - let mut working:i32 = (num*1e5).round(); + let mut working:i32 = (num*1e5).round() as i32; //hopethis does what's needed working<<=1; - if num < 0 { + if num < 0.0 { working = !working; } let mut bits:[bool;30] = [false;30]; @@ -66,4 +77,25 @@ fn enc_float(num:f64)->String{ bits[i] = working % 2 == 1; working >>=1; } + bits.chunks(5).rev() + .map(|bools|{ + let mut accu:u8 = 0; + for i in 0..5{ + accu += if bools[4-i]{ + 1 + } else {0}; + accu <<=1; + } + accu |= 0x20; + accu +=63; + char::from(accu) + }).collect::() + } + +#[derive(serde::Serialize)] +pub struct RouteDraw{ + pub color:String, + pub stops:Vec +} + diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..5d8bccd --- /dev/null +++ b/src/types.rs @@ -0,0 +1,9 @@ + +#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)] +pub struct Coords{ + pub lat:f64, + pub lon:f64 +} + +#[derive(Clone, Copy, Debug, Hash)] +pub enum Service{OCCT, BC}