1 /******************************************************************************
2  * $Id: wind_history.cpp, v1.0 2010/08/30 tom-r Exp $
3  *
4  * Project:  OpenCPN
5  * Purpose:  Dashboard Plugin
6  * Author:   Thomas Rauch
7  *
8  ***************************************************************************
9  *   Copyright (C) 2010 by David S. Register   *
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  *   This program is distributed in the hope that it will be useful,       *
17  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
19  *   GNU General Public License for more details.                          *
20  *                                                                         *
21  *   You should have received a copy of the GNU General Public License     *
22  *   along with this program; if not, write to the                         *
23  *   Free Software Foundation, Inc.,                                       *
24  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.             *
25  ***************************************************************************
26  */
27 
28 #include "wind_history.h"
29 #include "wx28compat.h"
30 
31 // For compilers that support precompilation, includes "wx/wx.h".
32 #include <wx/wxprec.h>
33 
34 #ifdef __BORLANDC__
35     #pragma hdrstop
36 #endif
37 
38 // for all others, include the necessary headers (this file is usually all you
39 // need because it includes almost all "standard" wxWidgets headers)
40 #ifndef WX_PRECOMP
41     #include <wx/wx.h>
42 #endif
43 
44 
45 //************************************************************************************************************************
46 // History of wind direction
47 //************************************************************************************************************************
48 
DashboardInstrument_WindDirHistory(wxWindow * parent,wxWindowID id,wxString title)49 DashboardInstrument_WindDirHistory::DashboardInstrument_WindDirHistory( wxWindow *parent, wxWindowID id, wxString title) :
50       DashboardInstrument(parent, id, title, OCPN_DBP_STC_TWD | OCPN_DBP_STC_TWS)
51 {     SetDrawSoloInPane(true);
52       m_MaxWindDir = -1;
53       m_WindDir = -1;
54       m_WindDirRange=90;
55       m_MaxWindSpd = 0;
56       m_WindSpeedUnit = _("--");
57       m_TotalMaxWindSpd = 0;
58       m_WindSpd = 0;
59       m_TopLineHeight=30;
60       m_SpdRecCnt=0;
61       m_DirRecCnt=0;
62       m_SpdStartVal=-1;
63       m_DirStartVal=-1;
64       m_IsRunning=false;
65       m_SampleCount=0;
66       m_LeftLegend=3;
67       m_RightLegend=3;
68       for (int idx = 0; idx < WIND_RECORD_COUNT; idx++) {
69         m_ArrayWindDirHistory[idx] = -1;
70         m_ArrayWindSpdHistory[idx] = -1;
71         m_ExpSmoothArrayWindSpd[idx] = -1;
72         m_ExpSmoothArrayWindDir[idx] = -1;
73         m_ArrayRecTime[idx]=wxDateTime::Now().GetTm( );
74         m_ArrayRecTime[idx].year=999;
75       }
76       alpha=0.01;  //smoothing constant
77       m_WindowRect=GetClientRect();
78       m_DrawAreaRect=GetClientRect();
79       m_DrawAreaRect.SetHeight(m_WindowRect.height-m_TopLineHeight-m_TitleHeight);
80 }
81 
GetSize(int orient,wxSize hint)82 wxSize DashboardInstrument_WindDirHistory::GetSize( int orient, wxSize hint )
83 {
84       wxClientDC dc(this);
85       int w;
86       dc.GetTextExtent(m_title, &w, &m_TitleHeight, 0, 0, g_pFontTitle);
87       if( orient == wxHORIZONTAL ) {
88         return wxSize( DefaultWidth, wxMax(m_TitleHeight+140, hint.y) );
89       }
90       else {
91         return wxSize( wxMax(hint.x, DefaultWidth), wxMax(m_TitleHeight+140, hint.y) );
92       }
93 }
SetData(int st,double data,wxString unit)94 void DashboardInstrument_WindDirHistory::SetData(int st, double data, wxString unit)
95 {
96   if (st == OCPN_DBP_STC_TWD || st == OCPN_DBP_STC_TWS) {
97     if (st == OCPN_DBP_STC_TWD) {
98       m_WindDir = data;
99 	  if (m_DirRecCnt <= 5){
100 		  m_DirStartVal += data;
101 		  m_DirRecCnt++;
102 	  }
103     }
104     if (st == OCPN_DBP_STC_TWS && (std::isnan(data) || data < 200.0)) {
105       m_WindSpd = data;
106 	  // if unit changes, reset everything ...
107 	  if (unit != m_WindSpeedUnit && m_WindSpeedUnit != _("--")) {
108 		  m_MaxWindDir = -1;
109 		  m_WindDir = -1;
110 		  m_WindDirRange = 90;
111 		  m_MaxWindSpd = 0;
112 		  m_TotalMaxWindSpd = 0;
113 		  m_WindSpd = 0;
114 		  m_SpdRecCnt = 0;
115 		  m_DirRecCnt = 0;
116 		  m_SpdStartVal = -1;
117 		  m_DirStartVal = -1;
118 		  m_IsRunning = false;
119 		  m_SampleCount = 0;
120 		  m_LeftLegend = 3;
121 		  m_RightLegend = 3;
122 		  for (int idx = 0; idx < WIND_RECORD_COUNT; idx++) {
123 			  m_ArrayWindDirHistory[idx] = -1;
124 			  m_ArrayWindSpdHistory[idx] = -1;
125 			  m_ExpSmoothArrayWindSpd[idx] = -1;
126 			  m_ExpSmoothArrayWindDir[idx] = -1;
127 			  m_ArrayRecTime[idx] = wxDateTime::Now().GetTm( );
128 			  m_ArrayRecTime[idx].year = 999;
129 		  }
130 	  }
131 	  m_WindSpeedUnit = unit;
132 	  if (m_SpdRecCnt <= 5){
133 		  m_SpdStartVal += data;
134 		  m_SpdRecCnt++;
135 	  }
136     }
137     if ( m_SpdRecCnt == 5 && m_DirRecCnt == 5) {
138       m_WindSpd=  m_SpdStartVal/5;
139       m_WindDir = m_DirStartVal/5;
140       m_oldDirVal=m_WindDir; // make sure we don't get a diff > or <180 in the initial run
141     }
142     //start working after we collected 5 records each, as start values for the smoothed curves
143     if (m_SpdRecCnt > 5 && m_DirRecCnt > 5) {
144       m_IsRunning=true;
145       m_SampleCount = m_SampleCount<WIND_RECORD_COUNT? m_SampleCount+1:WIND_RECORD_COUNT;
146       m_MaxWindDir = 0;
147       m_MinWindDir = 360;
148       m_MaxWindSpd = 0;
149       //data shifting
150       for (int idx = 1; idx < WIND_RECORD_COUNT; idx++) {
151         if (WIND_RECORD_COUNT-m_SampleCount <= idx)
152           m_MinWindDir = wxMin(m_ArrayWindDirHistory[idx],m_MinWindDir);
153         m_MaxWindDir = wxMax(m_ArrayWindDirHistory[idx-1],m_MaxWindDir);
154         m_MaxWindSpd   = wxMax(m_ArrayWindSpdHistory[idx-1],m_MaxWindSpd);
155         m_ArrayWindDirHistory[idx-1] = m_ArrayWindDirHistory[idx];
156         m_ArrayWindSpdHistory[idx-1] = m_ArrayWindSpdHistory[idx];
157         m_ExpSmoothArrayWindSpd[idx-1]=m_ExpSmoothArrayWindSpd[idx];
158         m_ExpSmoothArrayWindDir[idx-1]=m_ExpSmoothArrayWindDir[idx];
159         m_ArrayRecTime[idx-1]=m_ArrayRecTime[idx];
160       }
161       double diff=m_WindDir - m_oldDirVal;
162       if (diff < -270) {
163         m_WindDir+=360;
164       }
165       else
166       if( diff > 270) {
167         m_WindDir-=360;
168       }
169       m_ArrayWindDirHistory[WIND_RECORD_COUNT-1] = m_WindDir;
170       m_ArrayWindSpdHistory[WIND_RECORD_COUNT-1] = m_WindSpd;
171       if( m_SampleCount<2) {
172         m_ArrayWindSpdHistory[WIND_RECORD_COUNT-2] = m_WindSpd;
173         m_ExpSmoothArrayWindSpd[WIND_RECORD_COUNT-2]= m_WindSpd;
174         m_ArrayWindDirHistory[WIND_RECORD_COUNT-2] = m_WindDir;
175         m_ExpSmoothArrayWindDir[WIND_RECORD_COUNT-2]= m_WindDir;
176       }
177       m_ExpSmoothArrayWindSpd[WIND_RECORD_COUNT-1]=alpha*m_ArrayWindSpdHistory[WIND_RECORD_COUNT-2]+(1-alpha)*m_ExpSmoothArrayWindSpd[WIND_RECORD_COUNT-2];
178       m_ExpSmoothArrayWindDir[WIND_RECORD_COUNT-1]=alpha*m_ArrayWindDirHistory[WIND_RECORD_COUNT-2]+(1-alpha)*m_ExpSmoothArrayWindDir[WIND_RECORD_COUNT-2];
179       m_ArrayRecTime[WIND_RECORD_COUNT-1]=wxDateTime::Now().GetTm( );
180       m_oldDirVal=m_ExpSmoothArrayWindDir[WIND_RECORD_COUNT-1];
181       //include the new/latest value in the max/min value test too
182       m_MaxWindDir = wxMax(m_WindDir,m_MaxWindDir);
183       m_MinWindDir = wxMin(m_WindDir,m_MinWindDir);
184       m_MaxWindSpd   = wxMax(m_WindSpd,m_MaxWindSpd);
185       //get the overall max Wind Speed
186       m_TotalMaxWindSpd = wxMax(m_WindSpd,m_TotalMaxWindSpd);
187 
188       // set wind angle scale to full +/- 90° depending on the real max/min value recorded
189       SetMinMaxWindScale();
190     }
191   }
192 }
193 
Draw(wxGCDC * dc)194 void DashboardInstrument_WindDirHistory::Draw(wxGCDC* dc)
195 {
196    m_WindowRect = GetClientRect();
197    m_DrawAreaRect=GetClientRect();
198    m_DrawAreaRect.SetHeight(m_WindowRect.height-m_TopLineHeight-m_TitleHeight);
199    m_DrawAreaRect.SetX (m_LeftLegend+3);
200    DrawBackground(dc);
201    DrawForeground(dc);
202 }
203 
204 //*********************************************************************************
205 // determine and set  min and max values for the direction
206 //*********************************************************************************
SetMinMaxWindScale()207 void DashboardInstrument_WindDirHistory::SetMinMaxWindScale()
208 {
209   // set wind direction legend to full +/- 90° depending on the real max/min value recorded
210   // example : max wind dir. = 45°  ==> max = 90°
211   //           min wind dir. = 45°  ==> min = 0°
212   //first calculate the max wind direction
213   int fulldeg = m_MaxWindDir / 90; //we explicitly chop off the decimals by type conversion from double to int !
214   if(fulldeg == 0)
215     fulldeg=m_MaxWindDir<0 ?0:1;
216   else if (m_MaxWindDir>0)
217     fulldeg+=1;
218   m_MaxWindDir=fulldeg*90;
219   //now calculate the min wind direction
220   fulldeg = m_MinWindDir / 90;
221   if(fulldeg == 0)
222     fulldeg=m_MinWindDir<0 ?-1:0;
223   else
224     fulldeg=m_MinWindDir>0 ? fulldeg:(fulldeg-1);
225   m_MinWindDir=fulldeg*90;
226 
227   // limit the visible wind dir range to 360°; remove the extra range on the opposite side of the current wind dir value
228   m_WindDirRange=m_MaxWindDir-m_MinWindDir;
229   if (m_WindDirRange > 360) {
230     int diff2min=m_WindDir-m_MinWindDir;    //diff between min value and current value
231     int diff2max=m_MaxWindDir-m_WindDir;    //diff between max value and current value
232     if(diff2min > diff2max) {
233       while (m_WindDirRange > 360) {
234         m_MinWindDir+=90;
235         m_WindDirRange=m_MaxWindDir-m_MinWindDir;
236       }
237     }
238     if(diff2min < diff2max) {
239       while (m_WindDirRange > 360) {
240         m_MaxWindDir-=90;
241         m_WindDirRange=m_MaxWindDir-m_MinWindDir;
242       }
243     }
244   }
245 }
246 //*********************************************************************************
247 // wind direction legend
248 //*********************************************************************************
DrawWindDirScale(wxGCDC * dc)249 void  DashboardInstrument_WindDirHistory::DrawWindDirScale(wxGCDC* dc)
250 {
251   wxString label1,label2,label3,label4,label5;
252   wxColour cl;
253   wxPen pen;
254   int width, height;
255   cl=wxColour(204,41,41,255); //red, opague
256 
257   dc->SetTextForeground(cl);
258   dc->SetFont(*g_pFontSmall);
259   if(!m_IsRunning) {
260     label1=_T("---");
261     label2=_T("---");
262     label3=_T("---");
263     label4=_T("---");
264     label5=_T("---");
265   }
266   else {
267     // label 1 : legend for bottom line. By definition always w/o decimals
268     double tempdir=m_MinWindDir;
269     while (tempdir < 0)    tempdir+=360;
270     while (tempdir >= 360) tempdir-=360;
271     label1=GetWindDirStr(wxString::Format(_T("%.1f"), tempdir));
272     // label 2 : 1/4
273     tempdir=m_MinWindDir+m_WindDirRange/4.;
274     while (tempdir < 0)    tempdir+=360;
275     while (tempdir >= 360) tempdir-=360;
276     label2=GetWindDirStr(wxString::Format(_T("%.1f"), tempdir));
277     // label 3 : legend for center line
278     tempdir=m_MinWindDir+m_WindDirRange/2;
279     while (tempdir < 0)    tempdir+=360;
280     while (tempdir >= 360) tempdir-=360;
281     label3=GetWindDirStr(wxString::Format(_T("%.1f"), tempdir));
282     // label 4 :  3/4
283     tempdir=m_MinWindDir+m_WindDirRange*0.75;
284     while (tempdir < 0)    tempdir+=360;
285     while (tempdir >= 360) tempdir-=360;
286     label4=GetWindDirStr(wxString::Format(_T("%.1f"), tempdir));
287     // label 5 : legend for top line
288     tempdir=m_MaxWindDir;
289     while (tempdir < 0)    tempdir+=360;
290     while (tempdir >= 360) tempdir-=360;
291     label5=GetWindDirStr(wxString::Format(_T("%.1f"), tempdir));
292   }
293   //draw the legend with the labels; find the widest string and store it in m_RightLegend.
294   // m_RightLegend is the basis for the horizontal lines !
295   dc->GetTextExtent(label5, &width, &height, 0, 0, g_pFontSmall);
296   m_RightLegend=width;
297   dc->GetTextExtent(label4, &width, &height, 0, 0, g_pFontSmall);
298   m_RightLegend=wxMax(width,m_RightLegend);
299   dc->GetTextExtent(label3, &width, &height, 0, 0, g_pFontSmall);
300   m_RightLegend=wxMax(width,m_RightLegend);
301   dc->GetTextExtent(label2, &width, &height, 0, 0, g_pFontSmall);
302   m_RightLegend=wxMax(width,m_RightLegend);
303   dc->GetTextExtent(label1, &width, &height, 0, 0, g_pFontSmall);
304   m_RightLegend=wxMax(width,m_RightLegend);
305 
306   m_RightLegend+=4; //leave some space to the edge
307   dc->DrawText(label5, m_WindowRect.width-m_RightLegend, m_TopLineHeight-height/2);
308   dc->DrawText(label4, m_WindowRect.width-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height/4-height/2));
309   dc->DrawText(label3, m_WindowRect.width-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height/2-height/2));
310   dc->DrawText(label2, m_WindowRect.width-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.75-height/2));
311   dc->DrawText(label1, m_WindowRect.width-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height-height/2));
312 }
313 
314 //*********************************************************************************
315 // draw wind speed scale
316 //*********************************************************************************
DrawWindSpeedScale(wxGCDC * dc)317 void  DashboardInstrument_WindDirHistory::DrawWindSpeedScale(wxGCDC* dc)
318 {
319   wxString label1,label2,label3,label4,label5;
320   wxColour cl;
321   int width, height;
322   double val1;
323   double WindSpdScale;
324 
325   cl=wxColour(61,61,204,255);
326   dc->SetTextForeground(cl);
327   dc->SetFont(*g_pFontSmall);
328   //round maxWindSpd up to the next full knot; nicer view ...
329   m_MaxWindSpdScale=(int)m_MaxWindSpd + 1;
330   if(!m_IsRunning) {
331  	label1.Printf(_("--- %s"), m_WindSpeedUnit.c_str());
332 	label2 = label1;
333 	label3 = label1;
334 	label4 = label1;
335 	label5 = label1;
336   }
337   else {
338 /*we round the speed up to the next full knot ==> the top and bottom line have full numbers as legend (e.g. 23 kn -- 0 kn)
339  but the intermediate lines may have decimal values (e.g. center line : 23/2=11.5 or quarter line 23/4=5.75), so in worst case
340  we end up with 23 - 17.25 - 11.5 - 5.75 - 0
341  The goal is to draw the legend with decimals only, if we really have them !
342 */
343     // top legend for max wind
344 	label1.Printf(_T("%.0f %s"), m_MaxWindSpdScale,m_WindSpeedUnit.c_str());
345     // 3/4 legend
346     WindSpdScale=m_MaxWindSpdScale*3./4.;
347     // do we need a decimal ?
348     val1=(int)((WindSpdScale-(int)WindSpdScale)*100);
349     if(val1==25 || val1==75)  // it's a .25 or a .75
350 	  label2.Printf(_T("%.2f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
351 	else if (val1 == 50)
352 	  label2.Printf(_T("%.1f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
353     else
354 	  label2.Printf(_T("%.0f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
355     // center legend
356     WindSpdScale=m_MaxWindSpdScale/2.;
357     // center line can either have a .0 or .5 value !
358     if((int)(WindSpdScale*10) % 10 == 5)
359 	  label3.Printf(_T("%.1f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
360     else
361 	  label3.Printf(_T("%.0f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
362 
363     // 1/4 legend
364     WindSpdScale=m_MaxWindSpdScale/4.;
365     // do we need a decimal ?
366     val1=(int)((WindSpdScale-(int)WindSpdScale)*100);
367     if(val1==25 || val1==75)
368  	  label4.Printf(_T("%.2f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
369 	else if (val1 == 50)
370 	  label4.Printf(_T("%.1f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
371 	else
372 	  label4.Printf(_T("%.0f %s"), WindSpdScale, m_WindSpeedUnit.c_str());
373 
374     //bottom legend for min wind, always 0
375 	label5.Printf(_T("%.0f %s"), 0.0, m_WindSpeedUnit.c_str());
376   }
377   dc->GetTextExtent(label1, &m_LeftLegend, &height, 0, 0, g_pFontSmall);
378   dc->DrawText(label1, 4, (int)(m_TopLineHeight-height/2));
379   dc->GetTextExtent(label2, &width, &height, 0, 0, g_pFontSmall);
380   dc->DrawText(label2, 4, (int)(m_TopLineHeight+m_DrawAreaRect.height/4-height/2));
381   m_LeftLegend = wxMax(width,m_LeftLegend);
382   dc->GetTextExtent(label3, &width, &height, 0, 0, g_pFontSmall);
383   dc->DrawText(label3, 4, (int)(m_TopLineHeight+m_DrawAreaRect.height/2-height/2));
384   m_LeftLegend = wxMax(width,m_LeftLegend);
385   dc->GetTextExtent(label4, &width, &height, 0, 0, g_pFontSmall);
386   dc->DrawText(label4, 4, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.75-height/2));
387   m_LeftLegend = wxMax(width,m_LeftLegend);
388   dc->GetTextExtent(label5, &width, &height, 0, 0, g_pFontSmall);
389   dc->DrawText(label5, 4,  (int)(m_TopLineHeight+m_DrawAreaRect.height-height/2));
390   m_LeftLegend = wxMax(width,m_LeftLegend);
391   m_LeftLegend+=4;
392 }
393 
394 //*********************************************************************************
395 //draw background
396 //*********************************************************************************
DrawBackground(wxGCDC * dc)397 void DashboardInstrument_WindDirHistory::DrawBackground(wxGCDC* dc)
398 {
399   wxString label,label1,label2,label3,label4,label5;
400   wxColour cl;
401   wxPen pen;
402   //---------------------------------------------------------------------------------
403   // draw legends for speed and direction
404   //---------------------------------------------------------------------------------
405   DrawWindDirScale(dc);
406   DrawWindSpeedScale(dc);
407 
408   //---------------------------------------------------------------------------------
409   // horizontal lines
410   //---------------------------------------------------------------------------------
411   GetGlobalColor(_T("UBLCK"), &cl);
412   pen.SetColour(cl);
413   dc->SetPen(pen);
414   dc->DrawLine(m_LeftLegend+3, m_TopLineHeight, m_WindowRect.width-3-m_RightLegend, m_TopLineHeight); // the upper line
415   dc->DrawLine(m_LeftLegend+3, (int)(m_TopLineHeight+m_DrawAreaRect.height), m_WindowRect.width-3-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height));
416   pen.SetStyle(wxPENSTYLE_DOT);
417   dc->SetPen(pen);
418   dc->DrawLine(m_LeftLegend+3, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.25), m_WindowRect.width-3-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.25));
419   dc->DrawLine(m_LeftLegend+3, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.75), m_WindowRect.width-3-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.75));
420 #ifdef __WXMSW__
421   pen.SetStyle(wxPENSTYLE_SHORT_DASH);
422   dc->SetPen(pen);
423 #endif
424   dc->DrawLine(m_LeftLegend+3, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.5), m_WindowRect.width-3-m_RightLegend, (int)(m_TopLineHeight+m_DrawAreaRect.height*0.5));
425 }
426 
427 //***********************************************************************************
428 // convert numerical wind direction values to text, used in the wind direction legend
429 //***********************************************************************************
GetWindDirStr(wxString WindDir)430 wxString DashboardInstrument_WindDirHistory::GetWindDirStr(wxString WindDir)
431 {
432    if ( WindDir == _T("0.0") || WindDir == _T("360.0") )
433      return _("N");
434    else
435    if (WindDir == _T("22.5"))
436      return _("NNE");
437    else
438    if (WindDir == _T("45.0"))
439      return _("NE");
440    else
441    if (WindDir == _T("67.5"))
442      return _("ENE");
443    else
444    if (WindDir == _T("90.0"))
445      return _("E");
446    else
447    if (WindDir == _T("112.5"))
448      return _("ESE");
449    else
450    if (WindDir == _T("135.0"))
451      return _("SE");
452    else
453    if (WindDir == _T("157.5"))
454      return _("SSE");
455    else
456    if (WindDir == _T("180.0"))
457      return _("S");
458    else
459    if (WindDir == _T("202.5"))
460      return _("SSW");
461    else
462    if (WindDir == _T("225.0"))
463      return _("SW");
464    else
465    if (WindDir == _T("247.5"))
466      return _("WSW");
467    else
468    if (WindDir == _T("270.0"))
469      return _("W");
470    else
471    if (WindDir == _T("292.5"))
472      return _("WNW");
473    else
474    if (WindDir == _T("315.0"))
475      return _("NW");
476    else
477    if (WindDir == _T("337.5"))
478      return _("NNW");
479    else
480      return WindDir;
481 }
482 
483 //*********************************************************************************
484 //draw foreground
485 //*********************************************************************************
DrawForeground(wxGCDC * dc)486 void DashboardInstrument_WindDirHistory::DrawForeground(wxGCDC* dc)
487 {
488   wxColour col;
489   double ratioH;
490   int degw,degh;
491   int width,height,sec,min,hour;
492   double dir;
493   wxString WindAngle,WindSpeed;
494   wxPen pen;
495   wxString label;
496 
497   //---------------------------------------------------------------------------------
498   // wind direction
499   //---------------------------------------------------------------------------------
500   dc->SetFont(*g_pFontData);
501   col=wxColour(204,41,41,255); //red, opaque
502   dc->SetTextForeground(col);
503   if(!m_IsRunning)
504     WindAngle=_T("TWD ---");
505   else {
506     dir=m_WindDir;
507     while(dir > 360) dir-=360;
508     while(dir <0 ) dir+=360;
509     if ( !std::isnan(dir) )
510         WindAngle=wxString::Format(_T("TWD %3.0f"), dir)+DEGREE_SIGN;
511     else
512         WindAngle = wxString::Format(_T("TWD ---")) + DEGREE_SIGN;
513   }
514   dc->GetTextExtent(WindAngle, &degw, &degh, 0, 0, g_pFontData);
515   dc->DrawText(WindAngle, m_WindowRect.width-degw-m_RightLegend-3, m_TopLineHeight-degh);
516   pen.SetStyle(wxPENSTYLE_SOLID);
517   pen.SetColour(wxColour(204,41,41 ,96)); //red, transparent
518   pen.SetWidth(1);
519   dc->SetPen( pen );
520   ratioH = (double) m_DrawAreaRect.height / m_WindDirRange;
521   m_DrawAreaRect.SetWidth(m_WindowRect.width - 6 - m_LeftLegend-m_RightLegend);
522   m_ratioW = double(m_DrawAreaRect.width) / (WIND_RECORD_COUNT-1);
523 
524   //---------------------------------------------------------------------------------
525   // live direction data
526   //---------------------------------------------------------------------------------
527   wxPoint points[WIND_RECORD_COUNT+2], pointAngle_old;
528   pointAngle_old.x=3+m_LeftLegend;
529   pointAngle_old.y = m_TopLineHeight+m_DrawAreaRect.height - (m_ArrayWindDirHistory[0]-m_MinWindDir) * ratioH;
530   for (int idx = 1; idx < WIND_RECORD_COUNT; idx++) {
531     points[idx].x = idx * m_ratioW + 3 + m_LeftLegend;
532     points[idx].y = m_TopLineHeight+m_DrawAreaRect.height - (m_ArrayWindDirHistory[idx]-m_MinWindDir) * ratioH;
533     if(WIND_RECORD_COUNT-m_SampleCount <= idx && points[idx].y > m_TopLineHeight && pointAngle_old.y> m_TopLineHeight && points[idx].y <=m_TopLineHeight+m_DrawAreaRect.height && pointAngle_old.y<=m_TopLineHeight+m_DrawAreaRect.height)
534       dc->DrawLine( pointAngle_old.x, pointAngle_old.y, points[idx].x,points[idx].y );
535     pointAngle_old.x=points[idx].x;
536     pointAngle_old.y=points[idx].y;
537   }
538 
539   //---------------------------------------------------------------------------------
540   //exponential smoothing of direction
541   //---------------------------------------------------------------------------------
542   pen.SetStyle(wxPENSTYLE_SOLID);
543   pen.SetColour(wxColour(204,41,41 ,255));
544   pen.SetWidth(2);
545   dc->SetPen( pen );
546   pointAngle_old.x=3+m_LeftLegend;
547   pointAngle_old.y = m_TopLineHeight+m_DrawAreaRect.height - (m_ExpSmoothArrayWindDir[0]-m_MinWindDir) * ratioH;
548   for (int idx = 1; idx < WIND_RECORD_COUNT; idx++) {
549     points[idx].x = idx * m_ratioW + 3 + m_LeftLegend;
550     points[idx].y = m_TopLineHeight+m_DrawAreaRect.height - (m_ExpSmoothArrayWindDir[idx]-m_MinWindDir) * ratioH;
551     if(WIND_RECORD_COUNT-m_SampleCount <= idx && points[idx].y > m_TopLineHeight && pointAngle_old.y > m_TopLineHeight && points[idx].y <=m_TopLineHeight+m_DrawAreaRect.height && pointAngle_old.y<=m_TopLineHeight+m_DrawAreaRect.height)
552       dc->DrawLine( pointAngle_old.x, pointAngle_old.y, points[idx].x,points[idx].y );
553     pointAngle_old.x=points[idx].x;
554     pointAngle_old.y=points[idx].y;
555   }
556 
557   //---------------------------------------------------------------------------------
558   // wind speed
559   //---------------------------------------------------------------------------------
560   col=wxColour(61,61,204,255); //blue, opaque
561   dc->SetFont(*g_pFontData);
562   dc->SetTextForeground(col);
563   if ( !std::isnan(m_WindSpd))
564       WindSpeed=wxString::Format(_T("TWS %3.1f %s "), m_WindSpd, m_WindSpeedUnit.c_str());
565   else
566       WindSpeed = wxString::Format(_T("TWS --- %s "), m_WindSpeedUnit.c_str());
567 
568   dc->GetTextExtent(WindSpeed, &degw, &degh, 0, 0, g_pFontData);
569   dc->DrawText(WindSpeed, m_LeftLegend+3, m_TopLineHeight-degh);
570   dc->SetFont(*g_pFontLabel);
571   //determine the time range of the available data (=oldest data value)
572   int i=0;
573   while(m_ArrayRecTime[i].year == 999 && i<WIND_RECORD_COUNT-1) i++;
574   if (i == WIND_RECORD_COUNT -1) {
575     min=0;
576     hour=0;
577   }
578   else {
579     wxDateTime localTime( m_ArrayRecTime[i] );
580     min=localTime.GetMinute( );
581     hour=localTime.GetHour( );
582   }
583   dc->DrawText(wxString::Format(_("Max %.1f %s since %02d:%02d  Overall %.1f %s"), m_MaxWindSpd, m_WindSpeedUnit.c_str(), hour, min, m_TotalMaxWindSpd, m_WindSpeedUnit.c_str()), m_LeftLegend + 3 + 2 + degw, m_TopLineHeight - degh + 5);
584   pen.SetStyle(wxPENSTYLE_SOLID);
585   pen.SetColour(wxColour(61,61,204,96)); //blue, transparent
586   pen.SetWidth(1);
587   dc->SetPen( pen );
588   ratioH = (double)m_DrawAreaRect.height / m_MaxWindSpdScale;
589   wxPoint  pointsSpd[WIND_RECORD_COUNT+2],pointSpeed_old;
590   pointSpeed_old.x=m_LeftLegend+3;
591   pointSpeed_old.y = m_TopLineHeight+m_DrawAreaRect.height - m_ArrayWindSpdHistory[0] * ratioH;
592 
593   //---------------------------------------------------------------------------------
594   // live speed data
595   //---------------------------------------------------------------------------------
596   for (int idx = 1; idx < WIND_RECORD_COUNT; idx++) {
597     pointsSpd[idx].x = idx * m_ratioW + 3 + m_LeftLegend;
598     pointsSpd[idx].y = m_TopLineHeight+m_DrawAreaRect.height - m_ArrayWindSpdHistory[idx] * ratioH;
599     if(WIND_RECORD_COUNT-m_SampleCount <= idx && pointsSpd[idx].y > m_TopLineHeight && pointSpeed_old.y > m_TopLineHeight && pointsSpd[idx].y <=m_TopLineHeight+m_DrawAreaRect.height && pointSpeed_old.y<=m_TopLineHeight+m_DrawAreaRect.height)
600       dc->DrawLine( pointSpeed_old.x, pointSpeed_old.y, pointsSpd[idx].x,pointsSpd[idx].y );
601     pointSpeed_old.x=pointsSpd[idx].x;
602     pointSpeed_old.y=pointsSpd[idx].y;
603   }
604 
605   //---------------------------------------------------------------------------------
606   //exponential smoothing of speed
607   //---------------------------------------------------------------------------------
608   pen.SetStyle(wxPENSTYLE_SOLID);
609   pen.SetColour(wxColour(61,61,204,255)); //blue, opaque
610   pen.SetWidth(2);
611   dc->SetPen( pen );
612   pointSpeed_old.x=m_LeftLegend+3;
613   pointSpeed_old.y = m_TopLineHeight+m_DrawAreaRect.height - m_ExpSmoothArrayWindSpd[0] * ratioH;
614   for (int idx = 1; idx < WIND_RECORD_COUNT; idx++) {
615     pointsSpd[idx].x = idx * m_ratioW + 3 + m_LeftLegend;
616     pointsSpd[idx].y = m_TopLineHeight+m_DrawAreaRect.height - m_ExpSmoothArrayWindSpd[idx] * ratioH;
617     if(WIND_RECORD_COUNT-m_SampleCount <= idx && pointsSpd[idx].y > m_TopLineHeight && pointSpeed_old.y > m_TopLineHeight && pointsSpd[idx].y <=m_TopLineHeight+m_DrawAreaRect.height && pointSpeed_old.y<=m_TopLineHeight+m_DrawAreaRect.height)
618       dc->DrawLine( pointSpeed_old.x, pointSpeed_old.y, pointsSpd[idx].x,pointsSpd[idx].y );
619     pointSpeed_old.x=pointsSpd[idx].x;
620     pointSpeed_old.y=pointsSpd[idx].y;
621   }
622 
623   //---------------------------------------------------------------------------------
624   //draw vertical timelines every 5 minutes
625   //---------------------------------------------------------------------------------
626   GetGlobalColor(_T("UBLCK"), &col);
627   pen.SetColour(col);
628   pen.SetStyle(wxPENSTYLE_DOT);
629   dc->SetPen(pen);
630   dc->SetTextForeground(col);
631   dc->SetFont(*g_pFontSmall);
632   int done=-1;
633   wxPoint pointTime;
634   for (int idx = 0; idx < WIND_RECORD_COUNT; idx++) {
635     if(m_ArrayRecTime[idx].year!= 999) {
636         wxDateTime localTime( m_ArrayRecTime[i] );
637         hour = localTime.GetHour( );
638         sec = localTime.GetSecond();
639         min = localTime.GetMinute( );
640         if ( (hour*100+min) != done && (min % 5 == 0 ) && (sec == 0 || sec == 1) ) {
641         pointTime.x = idx * m_ratioW + 3 + m_LeftLegend;
642         dc->DrawLine( pointTime.x, m_TopLineHeight+1, pointTime.x,(m_TopLineHeight+m_DrawAreaRect.height+1) );
643         label.Printf(_T("%02d:%02d"), hour,min);
644         dc->GetTextExtent(label, &width, &height, 0, 0, g_pFontSmall);
645         dc->DrawText(label, pointTime.x-width/2, m_WindowRect.height-height);
646         done=hour*100+min;
647       }
648     }
649   }
650 }
651