using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; using System.Globalization; using System.Threading; using System.Diagnostics; using ScoutBase.Core; using ScoutBase.Stations; using ScoutBase.Elevation; using ScoutBase.Propagation; using GMap.NET; using GMap.NET.MapProviders; using GMap.NET.WindowsForms; using GMap.NET.WindowsForms.Markers; using OxyPlot; using OxyPlot.WindowsForms; using OxyPlot.Series; using OxyPlot.Axes; namespace AirScout { public partial class HorizonDlg : Form { new LocationDesignator Location; QRVDesignator QRV; LocalObstructionDesignator LocalObstruction; short Elevation; double AntennaHeight; PropagationHorizonDesignator Horizon; double CalculationDistance = 500; GMapOverlay horizons = new GMapOverlay("Horizons"); GMapRoute horizon = new GMapRoute("Horizon"); ToolTip TT; IWin32Window OwnerWin; // charting // horizon chart PlotView pv_Elevation_Polar = new PlotView(); PlotModel pm_Elevation_Polar = new PlotModel(); AngleAxis Elevation_Polar_X = new AngleAxis(); MagnitudeAxis Elevation_Polar_Y = new MagnitudeAxis(); LineSeries Elevation_Polar_Series = new LineSeries(); PlotView pv_Elevation_Cartesian = new PlotView(); PlotModel pm_Elevation_Cartesian = new PlotModel(); LinearAxis Elevation_Cartesian_X = new LinearAxis(); LinearAxis Elevation_Cartesian_Y = new LinearAxis(); LineSeries Elevation_Cartesian_Series = new LineSeries(); // horizon distance chart PlotView pv_Distance_Polar = new PlotView(); PlotModel pm_Distance_Polar = new PlotModel(); AngleAxis Distance_Polar_X = new AngleAxis(); MagnitudeAxis Distance_Polar_Y = new MagnitudeAxis(); LineSeries Distance_Polar_Series = new LineSeries(); PlotView pv_Distance_Cartesian = new PlotView(); PlotModel pm_Distance_Cartesian = new PlotModel(); LinearAxis Distance_Cartesian_X = new LinearAxis(); LinearAxis Distance_Cartesian_Y = new LinearAxis(); LineSeries Distance_Cartesian_Series = new LineSeries(); double Map_Left; double Map_Right; double Map_Top; double Map_Bottom; public HorizonDlg(string call, double lat, double lon, LocalObstructionDesignator localobstruction) { InitializeComponent(); Location = StationData.Database.LocationFindOrCreate(call, MaidenheadLocator.LocFromLatLon(lat, lon, false, 3)); QRV = StationData.Database.QRVFindOrCreateDefault(call, MaidenheadLocator.LocFromLatLon(lat, lon, false, 3), Properties.Settings.Default.Band); LocalObstruction = localobstruction; Elevation = ElevationData.Database[Location.Lat, Location.Lon, Properties.Settings.Default.ElevationModel]; AntennaHeight = (QRV.AntennaHeight != 0) ? QRV.AntennaHeight : StationData.Database.QRVGetDefaultAntennaHeight(Properties.Settings.Default.Band); NumberFormatInfo provider = new NumberFormatInfo(); provider.NumberDecimalSeparator = "."; provider.NumberGroupSeparator = ","; tb_Horizon_Lat.Text = Location.Lat.ToString("F8",provider); tb_Horizon_Lon.Text = Location.Lon.ToString("F8",provider); tb_Horizon_Elevation.Text = ElevationData.Database[Location.Lat, Location.Lon, Properties.Settings.Default.ElevationModel].ToString("F0",provider); tb_Horizon_Height.Text = AntennaHeight.ToString("F0",provider); tb_Horizon_K_Factor.Text = Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor.ToString("F2",provider); tb_Horizon_QRG.Text = Bands.GetStringValue(Properties.Settings.Default.Band); tb_Horizon_F1_Clearance.Text = Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].F1_Clearance.ToString("F2",provider); tb_Horizon_ElevationModel.Text = Properties.Settings.Default.ElevationModel.ToString(); // setting User Agent to fix Open Street Map issue 2016-09-20 GMap.NET.MapProviders.GMapProvider.UserAgent = "AirScout"; // clearing referrer URL issue 2019-12-14 gm_Horizon.MapProvider.RefererUrl = ""; // set initial settings for main map gm_Horizon.MapProvider = GMapProviders.Find(Properties.Settings.Default.Map_Provider); gm_Horizon.IgnoreMarkerOnMouseWheel = true; gm_Horizon.MinZoom = 0; gm_Horizon.MaxZoom = 20; gm_Horizon.Zoom = 8; gm_Horizon.DragButton = System.Windows.Forms.MouseButtons.Left; gm_Horizon.CanDragMap = true; gm_Horizon.ScalePen = new Pen(Color.Black, 3); gm_Horizon.MapScaleInfoEnabled = true; gm_Horizon.Overlays.Add(horizons); GMarkerGoogle gm = new GMarkerGoogle(new PointLatLng(Location.Lat, Location.Lon), GMarkerGoogleType.red_dot); gm.ToolTipText = Location.Call; horizons.Markers.Add(gm); horizon.Stroke = new Pen(Color.Red, 3); horizons.Routes.Add(horizon); this.Text = "Radio Horizon of " + Location.Call; // initialize charts InitializeCharts(); // activate Polar if nothing checked if (!Properties.Settings.Default.Horizon_Plot_Polar && !Properties.Settings.Default.Horizon_Plot_Cartesian && !Properties.Settings.Default.Horizon_Plot_Map) Properties.Settings.Default.Horizon_Plot_Polar = true; // show according child windows UpdateCharts(); // create ToolTip on this window TT = new ToolTip(); OwnerWin = gm_Horizon; HorizonDlg_SizeChanged(this, null); // set map bounds Map_Left = Location.Lon; Map_Right = Location.Lon; Map_Top = Location.Lat; Map_Bottom = Location.Lat; } private ELEVATIONMODEL GetElevationModel() { if (Properties.Settings.Default.Elevation_SRTM1_Enabled) return ELEVATIONMODEL.SRTM1; if (Properties.Settings.Default.Elevation_SRTM3_Enabled) return ELEVATIONMODEL.SRTM3; if (Properties.Settings.Default.Elevation_GLOBE_Enabled) return ELEVATIONMODEL.GLOBE; return ELEVATIONMODEL.NONE; } private void InitializeCharts() { // elevation polar chart pm_Elevation_Polar.PlotType = PlotType.Polar; pm_Elevation_Polar.Title = String.Empty; pm_Elevation_Polar.DefaultFontSize = (double)Properties.Settings.Default.Charts_FontSize; pv_Elevation_Polar.BackColor = Color.White; pv_Elevation_Polar.Dock = DockStyle.Fill; pv_Elevation_Polar.Model = pm_Elevation_Polar; // add axes pm_Elevation_Polar.Axes.Clear(); // add x-axis Elevation_Polar_X.MajorGridlineStyle = LineStyle.Solid; Elevation_Polar_X.MinorGridlineStyle = LineStyle.Dot; Elevation_Polar_X.Angle = 90; Elevation_Polar_X.MajorStep = 30; Elevation_Polar_X.MinorStep = 10; Elevation_Polar_X.Minimum = 0; Elevation_Polar_X.Maximum = 360; Elevation_Polar_X.StartPosition = 1; Elevation_Polar_X.EndPosition = 0; Elevation_Polar_X.StartAngle = 360 + 90; Elevation_Polar_X.EndAngle = 90; pm_Elevation_Polar.Axes.Add(Elevation_Polar_X); // add y-axis Elevation_Polar_Y.MajorGridlineStyle = LineStyle.Solid; Elevation_Polar_Y.MinorGridlineStyle = LineStyle.Dot; Elevation_Polar_Y.Angle = 0; Elevation_Polar_Y.Position = AxisPosition.Right; pm_Elevation_Polar.Axes.Add(Elevation_Polar_Y); // add series pm_Elevation_Polar.Series.Clear(); Elevation_Polar_Series.StrokeThickness = 2; Elevation_Polar_Series.LineStyle = LineStyle.Solid; Elevation_Polar_Series.Color = OxyColors.Blue; pm_Elevation_Polar.Series.Add(Elevation_Polar_Series); gb_Elevation.Controls.Add(pv_Elevation_Polar); // elevation cartesian chart pv_Elevation_Cartesian.BackColor = Color.White; pv_Elevation_Cartesian.Dock = DockStyle.Fill; pv_Elevation_Cartesian.Model = pm_Elevation_Cartesian; pm_Elevation_Cartesian.Title = String.Empty; pm_Elevation_Cartesian.DefaultFontSize = (double)Properties.Settings.Default.Charts_FontSize; // add axes pm_Elevation_Cartesian.Axes.Clear(); // add x-axis Elevation_Cartesian_X.MajorGridlineStyle = LineStyle.Solid; Elevation_Cartesian_X.MinorGridlineStyle = LineStyle.Dot; Elevation_Cartesian_X.MajorStep = 30; Elevation_Cartesian_X.MinorStep = 10; Elevation_Cartesian_X.Minimum = 0; Elevation_Cartesian_X.Maximum = 360; Elevation_Cartesian_X.Position = AxisPosition.Bottom; pm_Elevation_Cartesian.Axes.Add(Elevation_Cartesian_X); // add y-axis Elevation_Cartesian_Y.MajorGridlineStyle = LineStyle.Solid; Elevation_Cartesian_Y.MinorGridlineStyle = LineStyle.Dot; Elevation_Cartesian_Y.Position = AxisPosition.Left; pm_Elevation_Cartesian.Axes.Add(Elevation_Cartesian_Y); // add series pm_Elevation_Cartesian.Series.Clear(); Elevation_Cartesian_Series.StrokeThickness = 2; Elevation_Cartesian_Series.LineStyle = LineStyle.Solid; Elevation_Cartesian_Series.Color = OxyColors.Blue; pm_Elevation_Cartesian.Series.Add(Elevation_Cartesian_Series); gb_Elevation.Controls.Add(pv_Elevation_Cartesian); // distance polar chart pm_Distance_Polar.PlotType = PlotType.Polar; pm_Distance_Polar.Title = String.Empty; pm_Distance_Polar.DefaultFontSize = (double)Properties.Settings.Default.Charts_FontSize; pv_Distance_Polar.BackColor = Color.White; pv_Distance_Polar.Dock = DockStyle.Fill; pv_Distance_Polar.Model = pm_Distance_Polar; // add axes pm_Distance_Polar.Axes.Clear(); // add x-axis Distance_Polar_X.MajorGridlineStyle = LineStyle.Solid; Distance_Polar_X.MinorGridlineStyle = LineStyle.Dot; Distance_Polar_X.Angle = 90; Distance_Polar_X.MajorStep = 30; Distance_Polar_X.MinorStep = 10; Distance_Polar_X.Minimum = 0; Distance_Polar_X.Maximum = 360; Distance_Polar_X.StartPosition = 1; Distance_Polar_X.EndPosition = 0; Distance_Polar_X.StartAngle = 360 + 90; Distance_Polar_X.EndAngle = 90; pm_Distance_Polar.Axes.Add(Distance_Polar_X); // add y-axis Distance_Polar_Y.MajorGridlineStyle = LineStyle.Solid; Distance_Polar_Y.MinorGridlineStyle = LineStyle.Dot; Distance_Polar_Y.Angle = 0; Distance_Polar_Y.Position = AxisPosition.Right; pm_Distance_Polar.Axes.Add(Distance_Polar_Y); // add series pm_Distance_Polar.Series.Clear(); Distance_Polar_Series.StrokeThickness = 2; Distance_Polar_Series.LineStyle = LineStyle.Solid; Distance_Polar_Series.Color = OxyColors.Red; pm_Distance_Polar.Series.Add(Distance_Polar_Series); gb_Distance.Controls.Add(pv_Distance_Polar); // distance cartesian chart pv_Distance_Cartesian.BackColor = Color.White; pv_Distance_Cartesian.Dock = DockStyle.Fill; pv_Distance_Cartesian.Model = pm_Distance_Cartesian; pm_Distance_Cartesian.Title = String.Empty; pm_Distance_Cartesian.DefaultFontSize = (double)Properties.Settings.Default.Charts_FontSize; // add axes pm_Distance_Cartesian.Axes.Clear(); // add x-axis Distance_Cartesian_X.MajorGridlineStyle = LineStyle.Solid; Distance_Cartesian_X.MinorGridlineStyle = LineStyle.Dot; Distance_Cartesian_X.MajorStep = 30; Distance_Cartesian_X.MinorStep = 10; Distance_Cartesian_X.Minimum = 0; Distance_Cartesian_X.Maximum = 360; Distance_Cartesian_X.Position = AxisPosition.Bottom; pm_Distance_Cartesian.Axes.Add(Distance_Cartesian_X); // add y-axis Distance_Cartesian_Y.MajorGridlineStyle = LineStyle.Solid; Distance_Cartesian_Y.MinorGridlineStyle = LineStyle.Dot; Distance_Cartesian_Y.Position = AxisPosition.Left; pm_Distance_Cartesian.Axes.Add(Distance_Cartesian_Y); // add series pm_Distance_Cartesian.Series.Clear(); Distance_Cartesian_Series.StrokeThickness = 2; Distance_Cartesian_Series.LineStyle = LineStyle.Solid; Distance_Cartesian_Series.Color = OxyColors.Red; pm_Distance_Cartesian.Series.Add(Distance_Cartesian_Series); gb_Distance.Controls.Add(pv_Distance_Cartesian); } private void btn_Horizon_Calculate_Click(object sender, EventArgs e) { // disable buttons btn_Horizon_Calculate.Enabled = false; btn_Horizon_Export.Enabled = false; // let the background worker do the rest bw_Horizon_Calculate.RunWorkerAsync(); } private void btn_Horizon_Export_Click(object sender, EventArgs e) { try { SaveFileDialog Dlg = new SaveFileDialog(); Dlg.CheckPathExists = true; Dlg.FileName = Location.Call.ToUpper() + "_Horizon"; if (Properties.Settings.Default.Elevation_SRTM1_Enabled) Dlg.FileName = Dlg.FileName + "_SRTM1"; if (Properties.Settings.Default.Elevation_SRTM3_Enabled) Dlg.FileName = Dlg.FileName + "_SRTM3"; if (Properties.Settings.Default.Elevation_GLOBE_Enabled) Dlg.FileName = Dlg.FileName + "_GLOBE"; else Dlg.FileName = Dlg.FileName + "_NONE"; Dlg.FileName = Dlg.FileName + "_" + ElevationData.Database.GetDefaultStepWidth(GetElevationModel()).ToString("F0") + "m"; Dlg.FileName = Dlg.FileName + ".csv"; Dlg.DefaultExt = ".csv"; if (Dlg.ShowDialog() == DialogResult.OK) { using (StreamWriter sw = new StreamWriter(Dlg.FileName)) { sw.WriteLine("Bearing[deg];Eps_Min[deg];Distance[km];Elevation[m]"); for (int i = 0; i < 360; i++) { sw.WriteLine(i.ToString() + ";" + (Horizon.Horizon[i].Epsmin / Math.PI * 180).ToString("F8") + ";" + Horizon.Horizon[i].Dist.ToString("F8") + ";" + Horizon.Horizon[i].Elv.ToString("F8")); } } } } catch { // do nothing, if export is going wrong } } private void btn_Horizon_Cancel_Click(object sender, EventArgs e) { this.Close(); } private void HorizonDlg_SizeChanged(object sender, EventArgs e) { int width = this.Width - 50; gb_Elevation.Top = gb_Parameter.Height + 16; gb_Elevation.Left = 10; gb_Elevation.Width = width / 2; gb_Elevation.Height = this.Height - gb_Parameter.Height - pa_Buttons.Height - 70; gb_Distance.Top = gb_Parameter.Height + 16; gb_Distance.Left = gb_Elevation.Width + 30; gb_Distance.Width = width / 2; gb_Distance.Height = this.Height - gb_Parameter.Height - pa_Buttons.Height - 70; gb_Map.Left = 10; gb_Map.Top = gb_Parameter.Height + 16; gb_Map.Width = gb_Elevation.Width + gb_Distance.Width + 15; gb_Map.Height = this.Height - gb_Parameter.Height - pa_Buttons.Height - 70; } private void ClearAllDiagrams() { Elevation_Cartesian_Series.Points.Clear(); Elevation_Polar_Series.Points.Clear(); Distance_Cartesian_Series.Points.Clear(); Distance_Cartesian_Series.Points.Clear(); horizon.Clear(); } private void DrawHorizonPoint(int azimuth, HorizonPoint hp, bool closing = false) { Elevation_Polar_Series.Points.Add(new DataPoint(hp.Epsmin / Math.PI * 180.0, azimuth)); pm_Elevation_Polar.InvalidatePlot(true); if (!closing) Elevation_Cartesian_Series.Points.Add(new DataPoint(azimuth, hp.Epsmin / Math.PI * 180.0)); pm_Elevation_Cartesian.InvalidatePlot(true); Distance_Polar_Series.Points.Add(new DataPoint(hp.Dist, azimuth)); pm_Distance_Polar.InvalidatePlot(true); if (!closing) Distance_Cartesian_Series.Points.Add(new DataPoint(azimuth, hp.Dist)); pm_Distance_Cartesian.InvalidatePlot(true); LatLon.GPoint gp = LatLon.DestinationPoint(Location.Lat, Location.Lon, azimuth, hp.Dist); PointLatLng p = new PointLatLng(gp.Lat, gp.Lon); horizon.Points.Add(p); if (p.Lng < Map_Left) Map_Left = p.Lng; if (p.Lng > Map_Right) Map_Right = p.Lng; if (p.Lat < Map_Bottom) Map_Bottom = p.Lat; if (p.Lat > Map_Top) Map_Top = p.Lat; // toggle visible to redraw horizon.IsVisible = false; horizon.IsVisible = true; gm_Horizon.SetZoomToFitRect(RectLatLng.FromLTRB(Map_Left, Map_Top, Map_Right, Map_Bottom)); } private void bw_Horizon_Calculate_DoWork(object sender, DoWorkEventArgs e) { // name the thread for debugging if (String.IsNullOrEmpty(Thread.CurrentThread.Name)) Thread.CurrentThread.Name = Name + "_" + this.GetType().Name; try { Stopwatch st = new Stopwatch(); st.Start(); // clear all diagrams ClearAllDiagrams(); PropagationHorizonDesignator hd = PropagationData.Database.PropagationHorizonFindOrCreate( bw_Horizon_Calculate, Location.Lat, Location.Lon, Elevation + AntennaHeight, CalculationDistance, Bands.ToGHz(Properties.Settings.Default.Band), LatLon.Earth.Radius * Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].K_Factor, Properties.Settings.Default.Path_Band_Settings[Properties.Settings.Default.Band].F1_Clearance, ElevationData.Database.GetDefaultStepWidth(Properties.Settings.Default.ElevationModel), Properties.Settings.Default.ElevationModel, LocalObstruction); st.Stop(); bw_Horizon_Calculate.ReportProgress(-1, "Calculation of radio horizon of " + Location.Call + " finished, " + st.ElapsedMilliseconds + "ms."); // report complete horizon to main thread bw_Horizon_Calculate.ReportProgress(360, hd); } catch (Exception ex) { bw_Horizon_Calculate.ReportProgress(-1, ex.ToString()); } } private void bw_Horizon_Calculate_ProgressChanged(object sender, ProgressChangedEventArgs e) { if (e.ProgressPercentage < 0) { tsl_Main.Text = (string)e.UserState; } else if ((e.ProgressPercentage >= 0) && (e.ProgressPercentage <= 359)) { // draw single horizon point when new calculation is running HorizonPoint hp = (HorizonPoint)e.UserState; DrawHorizonPoint(e.ProgressPercentage, hp); } else if (e.ProgressPercentage == 360) { // store complete horizon Horizon = (PropagationHorizonDesignator)e.UserState; } } private void bw_Horizon_Calculate_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // clear all drawing and draw again when finished if (Horizon != null) { // clear all diagrams and maps ClearAllDiagrams(); // draw all points for (int j = 0; j < 360; j++) { DrawHorizonPoint(j, Horizon.Horizon[j]); } // draw first point again to close the circle in polar modes DrawHorizonPoint(0, Horizon.Horizon[0],true); } // enable radio buttons btn_Horizon_Calculate.Enabled = true; btn_Horizon_Export.Enabled = true; } private void HorizonDlg_FormClosing(object sender, FormClosingEventArgs e) { bw_Horizon_Calculate.CancelAsync(); } private void rb_Horizon_Plot_Cartesian_Click(object sender, EventArgs e) { rb_Horizon_Plot_Cartesian.Checked = true; rb_Horizon_Plot_Polar.Checked = false; rb_Horizon_Plot_Map.Checked = false; UpdateCharts(); } private void rb_Horizon_Plot_Polar_Click(object sender, EventArgs e) { rb_Horizon_Plot_Polar.Checked = true; rb_Horizon_Plot_Cartesian.Checked = false; rb_Horizon_Plot_Map.Checked = false; UpdateCharts(); } private void rb_Horizon_Plot_Map_Click(object sender, EventArgs e) { rb_Horizon_Plot_Map.Checked = true; rb_Horizon_Plot_Cartesian.Checked = false; rb_Horizon_Plot_Polar.Checked = false; UpdateCharts(); } private void UpdateCharts() { try { gb_Elevation.BringToFront(); if (rb_Horizon_Plot_Polar.Checked) { gb_Distance.Visible = true; gb_Elevation.Visible = true; gb_Map.Visible = false; pv_Elevation_Polar.Visible = true; pv_Elevation_Cartesian.Visible = false; pv_Distance_Polar.Visible = true; pv_Distance_Cartesian.Visible = false; } else if (rb_Horizon_Plot_Cartesian.Checked) { gb_Distance.Visible = true; gb_Elevation.Visible = true; gb_Map.Visible = false; pv_Elevation_Polar.Visible = false; pv_Elevation_Cartesian.Visible = true; pv_Distance_Polar.Visible = false; pv_Distance_Cartesian.Visible = true; } else if (rb_Horizon_Plot_Map.Checked) { gb_Distance.Visible = false; gb_Elevation.Visible = false; gb_Map.Visible = true; gm_Horizon.Position = new PointLatLng(Location.Lat, Location.Lon); } } catch ( Exception ex) { MapDlg.Log.WriteMessage(ex.ToString(), LogLevel.Error); } } private void gm_Horizon_MouseDown(object sender, MouseEventArgs e) { // get geographic coordinates of mouse pointer PointLatLng p = gm_Horizon.FromLocalToLatLng(e.X, e.Y); Point loc = e.Location; loc.X += gm_Horizon.Cursor.Size.Width; loc.Y += gm_Horizon.Cursor.Size.Height; int elv = ElevationData.Database.ElvMissingFlag; // try to get elevation data from distinct elevation model // start with detailed one if (Properties.Settings.Default.Elevation_SRTM1_Enabled && (elv == ElevationData.Database.ElvMissingFlag)) elv = ElevationData.Database[p.Lat, p.Lng, ELEVATIONMODEL.SRTM1, false]; if (Properties.Settings.Default.Elevation_SRTM3_Enabled && (elv == ElevationData.Database.ElvMissingFlag)) elv = ElevationData.Database[p.Lat, p.Lng, ELEVATIONMODEL.SRTM3, false]; if (Properties.Settings.Default.Elevation_GLOBE_Enabled && (elv == ElevationData.Database.ElvMissingFlag)) elv = ElevationData.Database[p.Lat, p.Lng, ELEVATIONMODEL.GLOBE, false]; // set it to zero if still invalid if (elv == ElevationData.Database.ElvMissingFlag) elv = 0; double bearing = LatLon.Bearing(Location.Lat, Location.Lon, p.Lat, p.Lng); double dist = LatLon.Distance(Location.Lat, Location.Lon, p.Lat, p.Lng); TT.Show("Lat: " + p.Lat.ToString("F8", CultureInfo.InvariantCulture) + "°\nLon: " + p.Lng.ToString("F8", CultureInfo.InvariantCulture) + "°\nElv: " + elv.ToString() + "m\n\nQRB: " + dist.ToString("F2", CultureInfo.InvariantCulture) + "km\nQTF: " + bearing.ToString("F0",CultureInfo.InvariantCulture) + "°", OwnerWin, loc); } private void gm_Horizon_MouseUp(object sender, MouseEventArgs e) { TT.Hide(OwnerWin); } } }