// // FlighState.swift // LearnMapKit // // Created by Jacky Jack on 30/06/2024. // import Foundation import Collections import SwiftUI class FlightTracker: Identifiable { var last_time_seen: Int = 0 var ICAOaddress = 0 var ICAOName_avaliable = false var ICAOName = "" var Position_avaliable = false @Published var long:Double = 0.0 //? remove published @Published var lat:Double = 0.0 //?remove published var FromTo_avaliable = false var flightFrom:String = "" var flightTo:String = "" var id: String { String(ICAOaddress) } var direction = 0.0 enum CodingKeys: String, CodingKey { case ICAOName case long case lat } func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.ICAOName, forKey: .ICAOName) try container.encode(self.long, forKey: .long) try container.encode(self.lat, forKey: .lat) } } class FlightState: ObservableObject { var timer: Timer? //default location currently for testing var fromFile: Bool = false var idx_flight:[Int:Int] = [:] @Published var flight:[FlightTracker] = [] //configuration options var sourceFile = true var default_file_path = "/Users/jackyjack/Downloads/2024_05_27_raw_adsb.txt" var process_per_second = 120 var sourceDump1090Server = false var dump1090address = "X.X.X.X" var dump1090port = 12345 //flight expiration var staleAfterSeconds: Int = 120 var expireAfterSeconds: Int = 300 init() { print("Init") var count = 0 //let ADSBtask = ADSBFileRunner(filename: "") //let adsb_net = ADSBNetRunner(address: dump1090address, port: dump1090port) //if sourceFile { // startSourceFile() //} //if sourceDump1090Server { // startDump1090() //} } init(filename: String) { fileMode(filepath: filename) startSourceFile() } init(hostname: String, port: Int) { networkMode(hostname: hostname, port: port) startDump1090() } func networkMode(hostname: String, port: Int) { print("network mode") self.dump1090address = hostname self.dump1090port = port self.sourceDump1090Server = true self.sourceFile = false } func networkMode(netconfig: Binding) { print("network mode") self.dump1090address = netconfig.servername.wrappedValue self.dump1090port = netconfig.serverport.wrappedValue self.sourceDump1090Server = true self.sourceFile = false } func fileMode(filepath: String) { print("file mode") self.default_file_path = filepath self.sourceFile = true self.sourceDump1090Server = false } func defaultMode() { print("default mode") self.sourceDump1090Server = true self.sourceFile = false } func defaultMode(hostname: String, port: Int) { print("default mode net") self.sourceDump1090Server = true self.dump1090address = hostname self.dump1090port = port self.sourceFile = false } func startSourceFile() { print("Start reading ADSB messages from \(self.default_file_path)") let adsb_file = ADSBFileRunner(filename: self.default_file_path) DispatchQueue.global(qos: .background).sync { print("Open file") adsb_file.openFile() adsb_file.readFile() } DispatchQueue.global(qos: .background).async { print("Start decoding data") adsb_file.decodeFromFile() print("Stop decoding data") } //once a second read some data from decoded queue timer = Timer.scheduledTimer( withTimeInterval: 1, repeats: true ) { _ in //get the 10 entries if there is if adsb_file.jobDone() { print("Decoding done let get some data \(adsb_file.getCount())") //if adsb_file if adsb_file.getCount() > self.process_per_second { let data = adsb_file.getPlainData(self.process_per_second) //print(data.getCount()) for _ in 0.. 0 { //print("Process onse a second") for _ in 0..= 0.0005 { f.direction = updateDirection(f.lat, f.long, lat, long) } f.lat = lat f.long = long if f.ICAOName_avaliable { print("Update location name: \(f.ICAOName) lat:\(f.lat) long:\(f.long)") } else { print("Update location addr: \(address) lat:\(f.lat) long:\(f.long)") } //flight.append(f) flight[idx] = f //self.objectWillChange.send() return } } //using as List //print("No update?") } func updateDirection(_ oldLat: Double, _ oldLong: Double, _ newLat: Double, _ newLong: Double) -> Double { let deltaLat = newLat - oldLat let deltaLong = newLong - oldLong let meanLat = (newLat + oldLat) * 0.5 * .pi / 180.0 let east = deltaLong * cos(meanLat) let north = deltaLat let radians = atan2(east,north) let degrees = radians * 180.0 / .pi return degrees < 0 ? degrees + 360.0 : degrees } func addIcaoName(_ address: Int, _ icaoname: String) { /* if flight[address] == nil { flight[address] = FlightTracker() flight[address]?.last_time_seen = Int(Date().timeIntervalSince1970) flight[address]?.ICAOName_avaliable = true flight[address]?.ICAOName = icaoname print("new flight name added \(icaoname)") return } else { if let f = flight[address] { f.last_time_seen = Int(Date().timeIntervalSince1970) if f.ICAOName_avaliable == false { f.ICAOName_avaliable = true f.ICAOName = icaoname print("flight timestamp updated") return } } }*/ if idx_flight[address] == nil { let f = FlightTracker() f.ICAOaddress = address f.last_time_seen = Int(Date().timeIntervalSince1970) f.ICAOName_avaliable = true f.ICAOName = icaoname flight.append(f) idx_flight[address] = flight.count-1 } else { if let idx = idx_flight[address] { print("Flights \(flight.count)") } } //print("icao name") } func addNewFlight() { } //loop over and if expired then remove func removeExpiredFlight() { let now = Int(Date().timeIntervalSince1970) flight.removeAll { now - $0.last_time_seen > expireAfterSeconds } idx_flight.removeAll(keepingCapacity: true) //rebuild index for (index, f) in flight.enumerated() { idx_flight[f.ICAOaddress] = index } } func flightAge(_ ft: FlightTracker) -> Int { let now: Int = Int(Date().timeIntervalSince1970) return max(0, now - ft.last_time_seen) } func isStaled(_ ft: FlightTracker) -> Bool { return flightAge(ft) >= self.staleAfterSeconds } }