Skip to content

Commit

Permalink
working live atc
Browse files Browse the repository at this point in the history
  • Loading branch information
Sequal32 committed Sep 15, 2020
0 parents commit 182e73a
Show file tree
Hide file tree
Showing 9 changed files with 618 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
cargo.lock
.vscode
15 changes: 15 additions & 0 deletions Cargo.toml
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"]}
10 changes: 10 additions & 0 deletions config.json
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"
}
131 changes: 131 additions & 0 deletions src/flightaware.rs
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();
}
}
82 changes: 82 additions & 0 deletions src/flightradar.rs
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,
}
83 changes: 83 additions & 0 deletions src/interpolate.rs
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;
}
}
Loading

0 comments on commit 182e73a

Please sign in to comment.