finished the basic api as far as building is concerned but might be buggy
This commit is contained in:
parent
aa3902f1a4
commit
645d18fc4e
9 changed files with 279 additions and 34 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
||||||
/target
|
/target
|
||||||
migration/target
|
migration/target
|
||||||
|
check.sh
|
||||||
|
GOOGLE_API_KEY
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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
82
src/google.rs
Normal 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
|
||||||
|
}
|
78
src/main.rs
78
src/main.rs
|
@ -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
70
src/path_calc.rs
Normal 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
|
||||||
|
}
|
30
src/route.rs
30
src/route.rs
|
@ -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)
|
||||||
|
|
40
src/stop.rs
40
src/stop.rs
|
@ -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
9
src/types.rs
Normal 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}
|
Loading…
Reference in a new issue