finished the basic api as far as building is concerned but might be buggy

This commit is contained in:
Pagwin 2024-02-17 22:26:33 -05:00
parent aa3902f1a4
commit 645d18fc4e
9 changed files with 279 additions and 34 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
/target /target
migration/target migration/target
check.sh
GOOGLE_API_KEY

1
Cargo.lock generated
View file

@ -628,6 +628,7 @@ name = "bus_api"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono",
"reqwest", "reqwest",
"sea-orm", "sea-orm",
"serde", "serde",

View file

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.79" anyhow = "1.0.79"
chrono = "0.4.34"
reqwest = "0.11.24" reqwest = "0.11.24"
sea-orm = "0.12.14" sea-orm = "0.12.14"
serde = { version = "1.0.196", features = ["derive"] } serde = { version = "1.0.196", features = ["derive"] }

82
src/google.rs Normal file
View file

@ -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<TravelPathInfo>{
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::<RouteResponse>(response.text().await?.as_str())?;
Ok(response.into())
}
impl From<RouteResponse> 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<Route>
}
#[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
}

View file

@ -2,11 +2,15 @@ extern crate tokio;
extern crate reqwest; extern crate reqwest;
extern crate tide; extern crate tide;
extern crate anyhow; extern crate anyhow;
extern crate chrono;
//extern crate sea_orm; //extern crate sea_orm;
mod state; mod state;
mod route; mod route;
mod stop; mod stop;
mod google;
mod types;
mod path_calc;
use tide::prelude::*; use tide::prelude::*;
@ -38,37 +42,77 @@ async fn main() -> anyhow::Result<()> {
/// ] /// ]
/// } /// }
async fn transit_path(mut req: tide::Request<state::State>)->tide::Result{ async fn transit_path(mut req: tide::Request<state::State>)->tide::Result{
Ok("".into()) 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: /// example return:
/// 23 /// 23
async fn transit_estimate(mut req: tide::Request<state::State>)->tide::Result{ async fn transit_estimate(mut req: tide::Request<state::State>)->tide::Result{
Ok("".into()) let query = req.query::<TransitEstimateQuery>()?;
} let route = match query.service.as_str(){
"OCCT"=>{
/// return the coord pairs as json and line color forgiven route id in query params let routes = route::occt_routes_get().await?;
/// Example return: let Some(route) = routes.into_iter().find(|route| route.id == query.route) else {return Ok(tide::Response::builder(400).body("Error: invalid id").into())};
/// {"color":"#0000ff", "stops":[[-75,23],[-21,72]]} route
async fn bus_route_draw(mut req: tide::Request<state::State>)->tide::Result{
match req.query::<RouteDrawQuery>()?.id.split("_").collect::<Vec<&str>>()[..] {
["OCCT", id] => {
let route = route::occt_route_get(id.parse());
}, },
["BC", id] => { "BC"=>{
let route = route::broome_county_route_get(id.parse()); 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<state::State>)->tide::Result{
let query = req.query::<RouteDrawQuery>()?;
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)] #[derive(serde::Deserialize)]
struct RouteDrawQuery{ struct RouteDrawQuery{
id: String id: u32,
service: String
} }

70
src/path_calc.rs Normal file
View file

@ -0,0 +1,70 @@
pub async fn bus_travel_time(route:crate::route::Route, from_idx:usize, to_idx:usize)->anyhow::Result<chrono::Duration>{
let coords:Vec<crate::types::Coords> = 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_measure<F:Fn(&&crate::stop::Stop)->u64+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<crate::route::Route>{
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<crate::route::Route>)>)->Vec<crate::route::Route>{
let (_, ret) = dist_route_pairs.into_iter().min_by_key(|(dist,_)|dist.clone()).unwrap();
ret
}

View file

@ -1,11 +1,13 @@
struct Route { #[derive(Hash, Clone)]
id:u32, pub struct Route {
name: String, pub id:u32,
short_name: String, pub name: String,
color: String, pub short_name: String,
poly_line: String, pub color: String,
stops: Vec<crate::stop::Stop> pub poly_line: String,
pub stops: Vec<crate::stop::Stop>,
pub service: crate::types::Service
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
struct OcctRoute { struct OcctRoute {
@ -44,7 +46,7 @@ struct BCRoute{
struct OcctWrap{ struct OcctWrap{
get_routes:Vec<OcctRoute> get_routes:Vec<OcctRoute>
} }
async fn occt_routes_get()->anyhow::Result<Vec<Route>>{ pub async fn occt_routes_get()->anyhow::Result<Vec<Route>>{
let resp = reqwest::get("http://binghamtonupublic.etaspot.net/service.php?service=get_routes&token=TESTING").await?; let resp = reqwest::get("http://binghamtonupublic.etaspot.net/service.php?service=get_routes&token=TESTING").await?;
let body = resp.text().await?; let body = resp.text().await?;
let OcctWrap{get_routes: routes} = serde_json::from_str(body.as_str())?; let OcctWrap{get_routes: routes} = serde_json::from_str(body.as_str())?;
@ -61,30 +63,32 @@ async fn occt_routes_get()->anyhow::Result<Vec<Route>>{
name:route.name, name:route.name,
poly_line:route.poly_line, poly_line:route.poly_line,
short_name: route.short_name, short_name: route.short_name,
stops stops,
service: crate::types::Service::OCCT
} }
}).collect(); }).collect();
Ok(routes) Ok(routes)
} }
async fn broom_county_routes_get()->anyhow::Result<Route>{ pub async fn broome_county_routes_get()->anyhow::Result<Vec<Route>>{
let resp = reqwest::get("https://bctransit.doublemap.com/map/v2/routes").await?; let resp = reqwest::get("https://bctransit.doublemap.com/map/v2/routes").await?;
let body = resp.text().await?; let body = resp.text().await?;
let routes:Vec<BCRoute> = serde_json::from_str(body.as_str())?; let routes:Vec<BCRoute> = serde_json::from_str(body.as_str())?;
let stops = crate::stop::BC_stops_get().await?; let stops = crate::stop::BC_stops_get().await?;
let routes = routes.into_iter().map(|route|{ let routes = routes.into_iter().map(|route|{
let stops = stops.iter() let stops:Vec<crate::stop::Stop> = stops.iter()
.filter(|stop|{ .filter(|stop|{
route.stops.iter().any(|id|*id==stop.id) route.stops.iter().any(|id|*id==stop.id)
}).map(Clone::clone).collect(); }).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{ Route{
id:route.id, id:route.id,
color:route.color, color:route.color,
name:route.name, name:route.name,
poly_line, poly_line,
short_name: route.short_name, short_name: route.short_name,
stops stops,
service: crate::types::Service::BC
} }
}).collect(); }).collect();
Ok(routes) Ok(routes)

View file

@ -1,10 +1,20 @@
#[derive(Clone)] #[derive(Clone, PartialEq, PartialOrd)]
pub struct Stop{ pub struct Stop{
pub id:u32, pub id:u32,
pub name:String, pub name:String,
pub lat: f64, pub lat: f64,
pub lon: f64, pub lon: f64,
} }
impl std::hash::Hash for Stop {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u32(self.id);
}
}
impl From<Stop> for crate::types::Coords {
fn from(value: Stop) -> Self {
crate::types::Coords {lat: value.lat, lon: value.lon}
}
}
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
struct OcctStop{ struct OcctStop{
@ -53,12 +63,13 @@ pub fn poly_encode_stops<I:IntoIterator<Item=Stop>>(stops:I)->String{
.map(|stop|{ .map(|stop|{
enc_float(stop.lat) enc_float(stop.lat)
}) })
.reduce(|s1,s2|s1+&s2) .reduce(|s1,s2|s1+&s2).unwrap_or("".into())
}
fn enc_float(num:f64)->String{ 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 //hopethis does what's needed
working<<=1; working<<=1;
if num < 0 { if num < 0.0 {
working = !working; working = !working;
} }
let mut bits:[bool;30] = [false;30]; let mut bits:[bool;30] = [false;30];
@ -66,4 +77,25 @@ fn enc_float(num:f64)->String{
bits[i] = working % 2 == 1; bits[i] = working % 2 == 1;
working >>=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::<String>()
} }
#[derive(serde::Serialize)]
pub struct RouteDraw{
pub color:String,
pub stops:Vec<crate::types::Coords>
}

9
src/types.rs Normal file
View file

@ -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}