-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 182e73a
Showing
9 changed files
with
618 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/target | ||
cargo.lock | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "liveatc" | ||
version = "0.1.0" | ||
authors = ["Sequal32 <[email protected]>"] | ||
edition = "2018" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
crossbeam-channel = "0.4" | ||
regex = "1" | ||
reqwest = {version = "0.10.8", features=["blocking"]} | ||
scraper = "0.12" | ||
serde_json = "1.0" | ||
serde = {version = "1.0", features=["derive"]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"upper_lat": 42.48, | ||
"upper_lon": -71.28, | ||
"bottom_lat": 42.26, | ||
"bottom_lon": -70.74, | ||
|
||
"floor": 0, | ||
"ceiling": 99999, | ||
"callsign": "BOS_GND" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
use std::sync::{Arc, Mutex}; | ||
|
||
use reqwest::blocking::Client; | ||
use regex; | ||
use serde_json::{Value, Map}; | ||
|
||
use crate::request::Request; | ||
|
||
const ENDPOINT: &str = "https://flightaware.com/live/flight/"; | ||
|
||
#[derive(Debug)] | ||
pub struct FlightPlan { | ||
pub origin: String, | ||
pub destination: String, | ||
pub equipment: String, | ||
pub speed: u64, | ||
pub altitude: u64, | ||
pub route: String, | ||
|
||
} | ||
|
||
fn get_origin(flight_data: &Map<String, Value>) -> Option<String> { | ||
Some(flight_data.get("origin")?.as_object()?.get("icao")?.as_str().unwrap_or_default().to_string()) | ||
} | ||
|
||
fn get_destination(flight_data: &Map<String, Value>) -> Option<String> { | ||
Some(flight_data.get("destination")?.as_object()?.get("icao")?.as_str().unwrap_or_default().to_string()) | ||
} | ||
|
||
fn get_equipment(flight_data: &Map<String, Value>) -> Option<String> { | ||
Some(flight_data.get("aircraft")?.as_object()?.get("type")?.as_str().unwrap_or_default().to_string()) | ||
} | ||
|
||
fn get_flightplan_from_json(data: &Value) -> Option<FlightPlan> { | ||
let flights = data.as_object()?.get("flights")?.as_object()?; | ||
let (_, first_flight) = flights.iter().next()?; | ||
|
||
let flight_data = first_flight.as_object()?; | ||
let flight_plan = flight_data.get("flightPlan")?.as_object()?; | ||
|
||
let origin = get_destination(flight_data).unwrap_or_default(); | ||
let destination = get_origin(flight_data).unwrap_or_default(); | ||
let equipment = get_equipment(flight_data).unwrap_or_default(); | ||
|
||
|
||
let speed = flight_plan.get("speed")?.as_u64().unwrap_or(0); | ||
let mut altitude = flight_plan.get("altitude")?.as_u64().unwrap_or(0); | ||
let route = flight_plan.get("route")?.as_str().unwrap_or("").to_string(); | ||
|
||
if altitude < 1000 {altitude = altitude * 100} | ||
|
||
return Some(FlightPlan { | ||
origin, | ||
equipment, | ||
destination, | ||
speed, | ||
altitude, | ||
route | ||
}); | ||
} | ||
|
||
pub struct FlightPlanResult { | ||
pub id: String, | ||
pub callsign: String, | ||
pub fp: FlightPlan | ||
} | ||
|
||
pub struct FlightAware { | ||
client: Arc<Mutex<Client>>, | ||
flightplan_request: Request<Result<FlightPlanResult, FlightAwareError>> | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum FlightAwareError { | ||
RequestFailed(String), | ||
ParseError(String), | ||
} | ||
|
||
impl FlightAware { | ||
pub fn new() -> Self { | ||
Self { | ||
client: Arc::new(Mutex::new(Client::new())), | ||
flightplan_request: Request::new() | ||
} | ||
} | ||
|
||
pub fn request_flightplan(&self, id: &str, callsign: &str) { | ||
let exp = regex::Regex::new(r"var trackpollBootstrap = (\{.+\});").unwrap(); | ||
|
||
let tx = self.flightplan_request.get_handle(); | ||
let callsign = callsign.to_string(); | ||
let id = id.to_string(); | ||
let client = self.client.clone(); | ||
std::thread::spawn(move || { | ||
let response = client.lock().unwrap().get(format!("{}{}", ENDPOINT, callsign).as_str()).send(); | ||
if !response.is_ok() { | ||
tx.send(Err(FlightAwareError::RequestFailed(callsign))).ok(); | ||
return | ||
} | ||
|
||
if let Ok(text) = response.unwrap().text() { | ||
let mut data: &str = ""; | ||
|
||
for cap in exp.captures(text.as_str()) { | ||
data = cap.get(1).unwrap().as_str(); | ||
break; | ||
} | ||
// | ||
if let Ok(data) = serde_json::from_str(data) { | ||
let data: Value = data; | ||
match get_flightplan_from_json(&data) { | ||
Some(data) => { | ||
tx.send(Ok(FlightPlanResult {callsign, id: id, fp: data})).ok(); | ||
return | ||
}, | ||
None => { | ||
tx.send(Err(FlightAwareError::ParseError(callsign))).ok(); | ||
return | ||
} | ||
} | ||
} | ||
} | ||
|
||
tx.send(Err(FlightAwareError::ParseError(callsign))).ok(); | ||
}); | ||
} | ||
|
||
pub fn get_next_flightplan(&self) -> Option<Result<FlightPlanResult, FlightAwareError>> { | ||
return self.flightplan_request.get_next(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use std::collections::HashMap; | ||
|
||
use reqwest::blocking; | ||
use serde_json::{self, Value}; | ||
use serde::Deserialize; | ||
|
||
const ENDPOINT: &str = "https://data-live.flightradar24.com/zones/fcgi/feed.js?faa=1&mlat=1&flarm=1&adsb=1&gnd=1&air=1&vehicles=1&estimated=1&gliders=1&stats=1&maxage=14400"; | ||
|
||
#[derive(Deserialize, Debug, Default, Clone)] | ||
pub struct AircraftData { | ||
pub mode_s_code: String, | ||
pub latitude: f32, | ||
pub longitude: f32, | ||
pub bearing: u32, | ||
pub altitude: i32, | ||
pub speed: u32, | ||
pub squawk_code: String, | ||
pub radar: String, | ||
pub model: String, | ||
pub registration: String, | ||
pub timestamp: u64, | ||
pub origin: String, | ||
pub destination: String, | ||
pub flight: String, | ||
pub is_on_ground: u8, | ||
pub rate_of_climb: i32, | ||
pub callsign: String, | ||
pub is_glider: u8, | ||
pub airline: String | ||
} | ||
|
||
pub struct FlightRadar { | ||
client: blocking::Client, | ||
radar_loc: Bounds | ||
} | ||
|
||
impl FlightRadar { | ||
pub fn new(radar_loc: Bounds) -> Self { | ||
Self { | ||
client: blocking::Client::new(), | ||
radar_loc | ||
} | ||
} | ||
|
||
pub fn get_aircraft(&self) -> Result<HashMap<String, AircraftData>, ()> { | ||
let response = self.client.get( | ||
format!("{}&bounds={:.2},{:.2},{:.2},{:.2}", | ||
ENDPOINT, | ||
self.radar_loc.lat1, | ||
self.radar_loc.lat2, | ||
self.radar_loc.lon1, | ||
self.radar_loc.lon2 | ||
).as_str() | ||
).send(); | ||
|
||
let mut return_data = HashMap::new(); | ||
|
||
if !response.is_ok() {return Err(())} | ||
|
||
if let Ok(text) = response.unwrap().text() { | ||
// Parse as JSON | ||
let data: Value = serde_json::from_str(text.as_str()).unwrap(); | ||
// Iterate through aircraft | ||
for (index, value) in data.as_object().unwrap() { | ||
// Skip over stats data like numbers and objects | ||
if !value.is_array() {continue} | ||
let data: AircraftData = serde_json::from_value(value.clone()).unwrap(); | ||
return_data.insert(index.clone(), data); | ||
} | ||
return Ok(return_data); | ||
} | ||
|
||
return Err(()) | ||
} | ||
} | ||
|
||
pub struct Bounds { | ||
pub lat1: f32, | ||
pub lon1: f32, | ||
pub lat2: f32, | ||
pub lon2: f32, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use std::time::Instant; | ||
|
||
const INTERPOLATE_OFFSET: f32 = 1.0; | ||
|
||
fn convert_miles_to_lat(miles: f32) -> f32{ | ||
return miles / 69.0 | ||
} | ||
|
||
fn convert_miles_to_lon(miles: f32) -> f32{ | ||
return miles / 54.6 | ||
} | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct LatLon { | ||
pub lat: f32, | ||
pub lon: f32 | ||
} | ||
|
||
#[derive(Debug, Default, Clone)] | ||
pub struct Vector2D { | ||
pub x: f32, | ||
pub y: f32 | ||
} | ||
|
||
impl Vector2D { | ||
pub fn from_heading_and_speed(heading: f32, speed: f32) -> Self { | ||
// Split speed into componenets | ||
// let angle = heading + 180 % 360; | ||
|
||
Self { | ||
x: heading.to_radians().cos() * speed, | ||
y: heading.to_radians().sin() * speed | ||
} | ||
} | ||
} | ||
|
||
pub struct InterpolatePosition { | ||
pos: LatLon, | ||
speed: LatLon, | ||
time: Instant, | ||
last_call: LatLon | ||
} | ||
|
||
impl Default for InterpolatePosition { | ||
fn default() -> Self { | ||
Self { | ||
time: Instant::now(), | ||
pos: LatLon {lat: 0.0, lon: 0.0}, | ||
speed: LatLon {lat: 0.0, lon: 0.0}, | ||
last_call: LatLon {lat: 0.0, lon: 0.0}, | ||
} | ||
} | ||
} | ||
|
||
impl InterpolatePosition { | ||
pub fn new(lat: f32, lon: f32, heading: u32, speed: u32) -> Self { | ||
let speed = Vector2D::from_heading_and_speed(heading as f32, speed as f32); | ||
let speed_in_lat_lon = LatLon { // Also to seconds | ||
lat: convert_miles_to_lat(speed.x) / 3600.0, | ||
lon: convert_miles_to_lon(speed.y) / 3600.0 | ||
}; | ||
|
||
Self { | ||
pos: LatLon {lat, lon}, | ||
last_call: LatLon {lat, lon}, | ||
speed: speed_in_lat_lon, | ||
time: Instant::now(), | ||
} | ||
} | ||
|
||
pub fn get(&mut self) -> &LatLon { | ||
let elasped = (self.time.elapsed().as_secs_f32() - INTERPOLATE_OFFSET).max(0.0); | ||
self.last_call = LatLon { | ||
lat: self.pos.lat + self.speed.lat * elasped, | ||
lon: self.pos.lon + self.speed.lon * elasped, | ||
}; | ||
return &self.last_call; | ||
} | ||
|
||
pub fn get_no_update(&self) -> &LatLon { | ||
return &self.last_call; | ||
} | ||
} |
Oops, something went wrong.