using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text; using System.Threading; using System.ComponentModel; using System.Windows; using System.Globalization; using System.Net; using System.IO; using AirScout.Core; using AirScout.Aircrafts; using AirScout.AircraftPositions; using AirScout.PlaneFeeds.Plugin; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ScoutBase.Core; using System.Diagnostics; using AirScout.PlaneFeeds.Plugin.MEFContract; namespace AirScout.PlaneFeeds { // enum for ReportProgress messages public enum PROGRESS { ERROR = -1, STATUS = 0, PLANES = 1, FINISHED = 100 } // enum for PlaneFeed status public enum STATUS { ERROR = -1, OK = 0 } [System.ComponentModel.DesignerCategory("")] [DefaultPropertyAttribute("Name")] public class PlaneFeed : BackgroundWorker { private PlaneInfoCache PlanePositions = new PlaneInfoCache(); private PlaneFeedWorkEventArgs Arguments; public PlaneFeed() : base () { WorkerSupportsCancellation = true; WorkerReportsProgress = true; } public STATUS Status; protected LogWriter Log = LogWriter.Instance; protected override void OnDoWork(DoWorkEventArgs e) { Log.WriteMessage("Started."); Arguments = (PlaneFeedWorkEventArgs)e.Argument; if (String.IsNullOrEmpty(Thread.CurrentThread.Name)) Thread.CurrentThread.Name = this.GetType().Name; // use PlaneInfoConverter for plausibility check PlaneInfoConverter C = new PlaneInfoConverter(); // check boundaries if ((Arguments.MaxLat <= Arguments.MinLat) || (Arguments.MaxLon <= Arguments.MinLon)) { Status = STATUS.ERROR; this.ReportProgress((int)PROGRESS.ERROR, "Area boundaries mismatch. Check your Covered Area parameters!"); Log.WriteMessage("Area boundaries mismatch. Check your Covered Area parameters!", LogLevel.Error); } else { if (Arguments.Feed == null) { Status = STATUS.ERROR; this.ReportProgress((int)PROGRESS.ERROR, "Plane feed plugin not found. Check your settings!"); Log.WriteMessage("Plane feed plugin not found. Check your settings!", LogLevel.Error); } else { do { try { Status = STATUS.OK; int interval = Arguments.Interval; // build arguments PlaneFeedPluginArgs feedargs = new PlaneFeedPluginArgs(); feedargs.AppDirectory = Arguments.AppDirectory; feedargs.AppDataDirectory = Arguments.AppDataDirectory; feedargs.LogDirectory = Arguments.LogDirectory; feedargs.TmpDirectory = Arguments.TmpDirectory; feedargs.DatabaseDirectory = Arguments.DatabaseDirectory; feedargs.PlanePositionsDirectory = Arguments.PlanePositionsDirectory; feedargs.MaxLat = Arguments.MaxLat; feedargs.MinLon = Arguments.MinLon; feedargs.MinLat = Arguments.MinLat; feedargs.MaxLon = Arguments.MaxLon; feedargs.MyLat = Arguments.MyLat; feedargs.MyLon = Arguments.MyLon; feedargs.DXLat = Arguments.DXLat; feedargs.DXLon = Arguments.DXLon; feedargs.MinAlt = Arguments.MinAlt; feedargs.MaxAlt = Arguments.MaxAlt; feedargs.KeepHistory = Arguments.KeepHistory; feedargs.InstanceID = Arguments.InstanceID; feedargs.SessionKey = Arguments.SessionKey; feedargs.GetKeyURL = Arguments.GetKeyURL; feedargs.LogPlanePositions = Arguments.LogPlanePositions; // do start procedure Arguments.Feed.Start(feedargs); // run inner loop do { // call plugin's interface to get the planes try { Stopwatch st = new Stopwatch(); st.Start(); // get plane raw data and do addtional checks PlaneFeedPluginPlaneInfoList acs = Arguments.Feed.GetPlanes(feedargs); PlaneInfoList planes = new PlaneInfoList(); PlaneInfoList invalids = new PlaneInfoList(); int total = acs.Count; int count = 0; int errors = 0; double track = 0; double dist = 0; foreach (PlaneFeedPluginPlaneInfo ac in acs) { // skip without error when on ground if (ac.Ground) continue; // copy raw data to new PlaneInfo object PlaneInfo plane = new PlaneInfo(); plane.Hex = ac.Hex; plane.Lat = ac.Lat; plane.Lon = ac.Lon; plane.Alt = ac.Alt; plane.Call = ac.Call; plane.Reg = ac.Reg; plane.Track = ac.Track; plane.Speed = ac.Speed; plane.Time = ac.Time; plane.From = ac.From; plane.To = ac.To; plane.VSpeed = ac.VSpeed; try { plane.Category = (PLANECATEGORY)ac.Category; } catch { plane.Category = PLANECATEGORY.NONE; } plane.Type = ac.Type; plane.Model = ac.Model; plane.Manufacturer = ac.Manufacturer; // start checks // assuming that at least a timestamp is set! // do basic check on hex --> is strictly needed as identifier if (!PlaneInfoChecker.Check_Hex(plane.Hex)) { // try to fill hex from reg if (!PlaneInfoChecker.Check_Reg(plane.Reg)) { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Hex]: " + plane.Hex, LogLevel.Warning); invalids.Add(plane); errors++; continue; } AircraftDesignator ad = AircraftData.Database.AircraftFindByReg(plane.Reg); if (ad == null) { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Reg]: " + plane.Reg, LogLevel.Warning); invalids.Add(plane); errors++; continue; } plane.Hex = ad.Hex; } // check latitude if (!PlaneInfoChecker.Check_Lat(plane.Lat)) { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Lat]: " + plane.Lat.ToString("F8", CultureInfo.InvariantCulture), LogLevel.Warning); invalids.Add(plane); errors++; continue; } // skip without error when latitude is out of scope if ((plane.Lat < Arguments.MinLat) || (plane.Lat > Arguments.MaxLat)) continue; // check longitude if (!PlaneInfoChecker.Check_Lon(plane.Lon)) { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Lon]: " + plane.Lon.ToString("F8", CultureInfo.InvariantCulture), LogLevel.Warning); invalids.Add(plane); errors++; continue; } // skip without error when longitude is out of scope if ((plane.Lon < Arguments.MinLon) || (plane.Lon > Arguments.MaxLon)) continue; // check altitude if (!PlaneInfoChecker.Check_Alt(plane.Alt)) { // try to recover altitude from previuos messages PlaneInfo info = null; if (PlanePositions.TryGetValue(plane.Hex, out info)) { plane.Alt = info.Alt; } else { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Alt]: " + plane.Alt.ToString("F8", CultureInfo.InvariantCulture), LogLevel.Warning); invalids.Add(plane); errors++; continue; } } // skip without error when altitude_ is out of bounds if ((plane.Alt_m < Arguments.MinAlt) || (plane.Alt_m > Arguments.MaxAlt)) { continue; } // check call if (!PlaneInfoChecker.Check_Call(plane.Call)) { // try to recover from cache if check fails or set it to [unknown] PlaneInfo info = null; if (PlanePositions.TryGetValue(plane.Hex, out info)) { plane.Call = info.Call; } else plane.Call = "[unknown]"; } // still unknown call --> try to recover last known call from database if (!PlaneInfoChecker.Check_Call(plane.Call)) { AircraftDesignator ad = AircraftData.Database.AircraftFindByHex(plane.Hex); if (ad != null) { plane.Call = ad.Call; } else plane.Call = "[unknown]"; } // check registration if (!PlaneInfoChecker.Check_Reg(plane.Reg)) { // try to recover from cache if check fails or set it to [unknown] PlaneInfo info = null; if (PlanePositions.TryGetValue(plane.Hex, out info)) { plane.Reg = info.Reg; } else plane.Reg = "[unknown]"; } // still unknown --> try to recover last known reg from database if (!PlaneInfoChecker.Check_Reg(plane.Reg)) { AircraftDesignator ad = AircraftData.Database.AircraftFindByHex(plane.Hex); if (ad != null) { plane.Reg = ad.Reg; } else plane.Reg = "[unknown]"; } // check speed if (!PlaneInfoChecker.Check_Track(plane.Track)) { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Track]: " + plane.Track.ToString("F8", CultureInfo.InvariantCulture), LogLevel.Warning); invalids.Add(plane); errors++; continue; } // check speed if (!PlaneInfoChecker.Check_Speed(plane.Speed)) { // try to recover speed from previous messages PlaneInfo info = null; if (PlanePositions.TryGetValue(plane.Hex, out info)) { plane.Speed = info.Speed; } else { if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft data received [Speed]: " + plane.Speed.ToString("F8", CultureInfo.InvariantCulture), LogLevel.Warning); invalids.Add(plane); errors++; continue; } } // check type if (!PlaneInfoChecker.Check_Type(plane.Type)) { AircraftDesignator ad = AircraftData.Database.AircraftFindByHex(plane.Hex); if (ad != null) { plane.Type = ad.TypeCode; // getrest of info later later } else { // set all type info to unknown plane.Type = "[unknown]"; plane.Model = "[unknown]"; plane.Manufacturer = "[unknown]"; plane.Category = PLANECATEGORY.NONE; } } // try to recover type info from database if check fails or unknown if (!PlaneInfoChecker.Check_Manufacturer(plane.Manufacturer) || !PlaneInfoChecker.Check_Model(plane.Model) || (plane.Manufacturer == "[unkonwn]") || (plane.Model == "[unknown]")) { AircraftTypeDesignator td = AircraftData.Database.AircraftTypeFindByICAO(plane.Type); if (td != null) { plane.Manufacturer = td.Manufacturer; plane.Model = td.Model; plane.Category = td.Category; } else { plane.Manufacturer = "[unknown]"; plane.Model = "[unknown]"; plane.Category = PLANECATEGORY.NONE; } } // remove manufacturer info if part of model description if (plane.Model.StartsWith(plane.Manufacturer)) plane.Model = plane.Model.Remove(0, plane.Manufacturer.Length).Trim(); // check position against estimated position from last konwn if possible PlaneInfo estplane = null; PlaneInfo lastplane = null; if (PlanePositions.ContainsKey(plane.Hex)) { lastplane = PlanePositions[plane.Hex]; if ((plane.Time - lastplane.Time).TotalSeconds > 300) lastplane = null; estplane = PlanePositions.Get(plane.Hex, plane.Time, 5); } if (Arguments.ExtendedPlausibilityCheck_Enable && (lastplane != null) && (estplane != null)) { // estimate the track value from location change track = LatLon.Bearing(lastplane.Lat, lastplane.Lon, plane.Lat, plane.Lon); // estimate the distance between estimated position and reported position dist = LatLon.Distance(estplane.Lat, estplane.Lon, plane.Lat, plane.Lon); // Check track if (Math.Abs(((track <= 180) ? track : 360 - track) - ((plane.Track <= 180) ? plane.Track : 360 - plane.Track)) > 45) { plane.Track = track; // report error if (Arguments.LogErrors) Log.WriteMessage("Incorrect aircraft track received [(" + lastplane.Track.ToString("F0") + "<>" + plane.Track.ToString("F0"), LogLevel.Warning); invalids.Add(plane); errors++; continue; } // check distance if (Math.Abs(dist) > Arguments.ExtendedPlausiblityCheck_MaxErrorDist * (plane.Time - lastplane.Time).TotalMinutes) { Console.WriteLine("[Error " + errors + "] : Distance " + dist); // report error if (Arguments.LogErrors) { invalids.Add(plane); Log.WriteMessage("Incorrect aircraft position received [(" + lastplane.Lat.ToString("F8") + "," + lastplane.Lon.ToString("F8") + ")<" + dist.ToString("F0") + "km>(" + plane.Lat.ToString("F8") + "," + plane.Lon.ToString("F8") + ")]: " + plane.ToString(), LogLevel.Warning); } invalids.Add(plane); errors++; continue; } } if (feedargs.LogPlanePositions) { // extended logging string filename = Path.Combine(feedargs.PlanePositionsDirectory, plane.Hex + ".csv"); if (!File.Exists(filename)) { File.WriteAllText(filename, "Time;TimeD;Hex;Lat;LatD;Lon;LonD;Alt;AltD;Track;TrackD;Speed;SpeedD;Call;Reg;From;To;VSpeed;CalcTrack;CalcDist" + Environment.NewLine); } try { double timed = 0; double latd = 0; double lond = 0; double altd = 0; double spdd = 0; double trkd = 0; if (lastplane != null) { timed = (lastplane.Time - plane.Time).TotalSeconds; latd = lastplane.Lat - plane.Lat; lond = lastplane.Lon - plane.Lon; altd = lastplane.Alt - plane.Alt; spdd = lastplane.Speed - plane.Speed; trkd = lastplane.Track - plane.Track; } string csv = plane.Time + ";" + timed + ";" + plane.Hex + ";" + plane.Lat + ";" + latd + ";" + plane.Lon + ";" + lond + ";" + plane.Alt + ";" + altd + ";" + plane.Track + ";" + trkd + ";" + plane.Speed + ";" + spdd + ";" + plane.Call + ";" + plane.Reg + ";" + plane.From + ";" + plane.To + ";" + plane.VSpeed + ";" + track + ";" + dist + Environment.NewLine; File.AppendAllText(filename, csv); } catch { } } // all checks successfully done --> add plane to list planes.Add(plane); count++; // cancel thread if requested if (this.CancellationPending) return; } // update local cache this.PlanePositions.BulkInsertOrUpdateIfNewer(planes); // report planes to main program this.ReportProgress((int)PROGRESS.PLANES, planes); // update global database AircraftData.Database.PlaneInfoBulkInsertOrUpdateIfNewer(this, planes); // update position database if enabled if (Arguments.KeepHistory) AircraftPositionData.Database.PlaneInfoBulkInsertOrUpdateIfNewer(planes); st.Stop(); string msg = "[" + DateTime.UtcNow.ToString("HH:mm:ss") + "] " + total.ToString() + " Positions updated from " + Arguments.Feed.Name + ", " + st.ElapsedMilliseconds.ToString() + " ms. OK: " + count.ToString() + ", Errors: " + errors.ToString(); this.ReportProgress((int)PROGRESS.STATUS, msg); // write all planes to file try { // simple logging using (StreamWriter sw = new StreamWriter(Path.Combine(Arguments.TmpDirectory, "planes.csv"))) { sw.WriteLine("Time;Hex;Lat;Lon;Alt;Track;Speed;Call;Reg;From;To;VSpeed"); foreach (PlaneInfo plane in planes) { sw.WriteLine(plane.Time + ";" + plane.Hex + ";" + plane.Lat + ";" + plane.Lon + ";" + plane.Alt + ";" + plane.Track + ";" + plane.Speed + ";" + plane.Call + ";" + plane.Reg + ";" + plane.From + ";" + plane.To + ";" + plane.VSpeed); } } } catch (Exception ex) { // do nothing } } catch (Exception ex) { Status = STATUS.ERROR; this.ReportProgress((int)PROGRESS.ERROR, "Plane Feed Execption: " + ex.Message); Log.WriteMessage(ex.ToString(), LogLevel.Error); } // wait for next execution int i = 0; while (!CancellationPending && (i < interval)) { Thread.Sleep(1000); i++; } } while (!CancellationPending); // do stop procedure Arguments.Feed.Stop(feedargs); } catch (Exception ex) { Status = STATUS.ERROR; this.ReportProgress((int)PROGRESS.ERROR, "Plane Feed Execption: " + ex.Message); Log.WriteMessage(ex.ToString(), LogLevel.Error); Console.WriteLine("Plane Feed Execption: " + ex.ToString(), LogLevel.Error); } } while (!this.CancellationPending); } } this.ReportProgress((int)PROGRESS.FINISHED); Log.WriteMessage("Finished."); } protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e) { // do stop procedure try { Arguments.Feed.Stop(null); } catch (Exception ex) { Log.WriteMessage(ex.ToString()); } base.OnRunWorkerCompleted(e); } } }