1  /******************************************************************************
2  *
3  * Project:  OpenCPN
4  * Purpose:  GRIB Object
5  * Author:   David Register
6  *
7  ***************************************************************************
8  *   Copyright (C) 2014 by David S. Register                               *
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  *   This program is distributed in the hope that it will be useful,       *
16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
18  *   GNU General Public License for more details.                          *
19  *                                                                         *
20  *   You should have received a copy of the GNU General Public License     *
21  *   along with this program; if not, write to the                         *
22  *   Free Software Foundation, Inc.,                                       *
23  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
24  ***************************************************************************
25  *
26  */
27 
28 #include "wx/wxprec.h"
29 
30 #ifndef  WX_PRECOMP
31   #include "wx/wx.h"
32 #endif //precompiled headers
33 
34 #include <wx/glcanvas.h>
35 #include <wx/graphics.h>
36 #include <wx/progdlg.h>
37 #include "pi_ocpndc.h"
38 
39 #ifdef __OCPN__ANDROID__
40 #include "qdebug.h"
41 #include "pi_shaders.h"
42 #endif
43 
44 #include "GribUIDialog.h"
45 #include "GribOverlayFactory.h"
46 
47 extern int m_Altitude;
48 extern bool g_bpause;
49 
50 enum GRIB_OVERLAP { _GIN, _GON, _GOUT };
51 
52 // Calculates if two boxes intersect. If so, the function returns _ON.
53 // If they do not intersect, two scenario's are possible:
54 // other is outside this -> return _OUT
55 // other is inside this -> return _IN
Intersect(PlugIn_ViewPort * vp,double lat_min,double lat_max,double lon_min,double lon_max,double Marge)56 static GRIB_OVERLAP Intersect( PlugIn_ViewPort *vp, double lat_min, double lat_max, double lon_min,
57                    double lon_max, double Marge )
58 {
59 
60     if( ( ( vp->lon_min - Marge ) > ( lon_max + Marge ) )
61             || ( ( vp->lon_max + Marge ) < ( lon_min - Marge ) )
62             || ( ( vp->lat_max + Marge ) < ( lat_min - Marge ) )
63             || ( ( vp->lat_min - Marge ) > ( lat_max + Marge ) ) ) return _GOUT;
64 
65     // Check if other.bbox is inside this bbox
66     if( ( vp->lon_min <= lon_min ) && ( vp->lon_max >= lon_max ) && ( vp->lat_max >= lat_max )
67             && ( vp->lat_min <= lat_min ) ) return _GIN;
68 
69     // Boundingboxes intersect
70     return _GON;
71 }
72 
73 // Is the given point in the vp ??
PointInLLBox(PlugIn_ViewPort * vp,double x,double y)74 static bool PointInLLBox( PlugIn_ViewPort *vp, double x, double y )
75 {
76     double m_miny = vp->lat_min;
77     double m_maxy = vp->lat_max;
78     if(y < m_miny || y > m_maxy)
79         return FALSE;
80 
81     double m_minx = vp->lon_min;
82     double m_maxx = vp->lon_max;
83 
84     if( x < m_maxx - 360.)
85         x +=  360;
86     else if(x > m_minx + 360.)
87         x -= 360;
88 
89     if (x < m_minx || x > m_maxx)
90         return FALSE;
91 
92     return TRUE;
93 }
94 
95 #if 0
96 static wxString MToString( int DataCenterModel )
97 {
98     switch( DataCenterModel ) {
99     case NOAA_GFS: return  _T("NOAA_GFS");
100     case NOAA_NCEP_WW3: return  _T("NOAA_NCEP_WW3");
101     case NOAA_NCEP_SST: return  _T("NOAA_NCEP_SST");
102     case NOAA_RTOFS: return  _T("NOAA_RTOFS");
103     case FNMOC_WW3_GLB: return  _T("FNMOC_WW3");
104     case FNMOC_WW3_MED: return  _T("FNMOC_WW3");
105     case NORWAY_METNO: return  _T("NORWAY_METNO");
106     default : return  _T("OTHER_DATA_CENTER");
107     }
108 }
109 #endif
110 
111 #if 0
112 static wxString TToString( const wxDateTime date_time, const int time_zone )
113 {
114     wxDateTime t( date_time );
115     t.MakeFromTimezone( wxDateTime::UTC );
116     if( t.IsDST() ) t.Subtract( wxTimeSpan( 1, 0, 0, 0 ) );
117     switch( time_zone ) {
118         case 0: return t.Format( _T(" %a %d-%b-%Y  %H:%M "), wxDateTime::Local ) + _T("LOC");//:%S
119         case 1:
120         default: return t.Format( _T(" %a %d-%b-%Y %H:%M  "), wxDateTime::UTC ) + _T("UTC");
121     }
122 }
123 #endif
124 
125 #ifdef ocpnUSE_GL
126 static GLuint texture_format = 0;
127 #endif
128 
129 #if 0
130 static GLboolean QueryExtension( const char *extName )
131 {
132     /*
133      ** Search for extName in the extensions string. Use of strstr()
134      ** is not sufficient because extension names can be prefixes of
135      ** other extension names. Could use strtok() but the constant
136      ** string returned by glGetString might be in read-only memory.
137      */
138     char *p;
139     char *end;
140     int extNameLen;
141 
142     extNameLen = strlen( extName );
143 
144     p = (char *) glGetString( GL_EXTENSIONS );
145     if( NULL == p ) {
146         return GL_FALSE;
147     }
148 
149     end = p + strlen( p );
150 
151     while( p < end ) {
152         int n = strcspn( p, " " );
153         if( ( extNameLen == n ) && ( strncmp( extName, p, n ) == 0 ) ) {
154             return GL_TRUE;
155         }
156         p += ( n + 1 );
157     }
158     return GL_FALSE;
159 }
160 
161 #if defined(__WXMSW__)
162 #define systemGetProcAddress(ADDR) wglGetProcAddress(ADDR)
163 #elif defined(__WXOSX__)
164 #include <dlfcn.h>
165 #define systemGetProcAddress(ADDR) dlsym( RTLD_DEFAULT, ADDR)
166 #else
167 #define systemGetProcAddress(ADDR) glXGetProcAddress((const GLubyte*)ADDR)
168 #endif
169 
170 #endif
171 
pushLine(float x0,float y0,float x1,float y1)172 void LineBuffer::pushLine( float x0, float y0, float x1, float y1 )
173 {
174     buffer.push_back(x0);
175     buffer.push_back(y0);
176     buffer.push_back(x1);
177     buffer.push_back(y1);
178 }
179 
pushPetiteBarbule(int b,int l)180 void LineBuffer::pushPetiteBarbule( int b, int l )
181 {
182     int tilt = (l * 100) / 250;
183     pushLine( b, 0, b + tilt,  l );
184 }
185 
pushGrandeBarbule(int b,int l)186 void LineBuffer::pushGrandeBarbule( int b, int l )
187 {
188     int tilt = (l * 100) / 250;
189     pushLine( b, 0, b + tilt, l );
190 }
191 
pushTriangle(int b,int l)192 void LineBuffer::pushTriangle( int b, int l )
193 {
194     int dim = (l * 100) / 250;
195     pushLine( b, 0, b + dim, l );
196     pushLine( b + (dim*2), 0, b + dim, l );
197 }
198 
Finalize()199 void LineBuffer::Finalize()
200 {
201     count = buffer.size() / 4;
202     lines = new float[buffer.size()];
203     int i = 0;
204     for(std::list <float>::iterator it = buffer.begin(); it != buffer.end(); it++)
205         lines[i++] = *it;
206 };
207 
adjustSpacing(int dialogSetSpacing)208 int  adjustSpacing( int dialogSetSpacing )
209 {
210 #ifdef __OCPN__ANDROID__
211     // Treat the slider control as a percentage value.
212     // Maximum space (100%) is established as one-half of the smaller of screen dismensions x and y.
213     wxSize sz = GetOCPNCanvasWindow()->GetClientSize();
214     int sizeMin = wxMin(sz.x, sz.y);
215     int space = ((double)dialogSetSpacing) * (sizeMin / 2) / 100;
216     //qDebug() << "Space: " << dialogSetSpacing << sizeMin << space;
217     return space;
218 
219 #else
220     return dialogSetSpacing;
221 #endif
222 }
223 
224 
225 //----------------------------------------------------------------------------------------------------------
226 //    Grib Overlay Factory Implementation
227 //----------------------------------------------------------------------------------------------------------
GRIBOverlayFactory(GRIBUICtrlBar & dlg)228 GRIBOverlayFactory::GRIBOverlayFactory( GRIBUICtrlBar &dlg )
229     : m_dlg(dlg), m_Settings(dlg.m_OverlaySettings)
230 {
231 
232 #ifdef __WXQT__
233     wxFont fo = GetOCPNGUIScaledFont_PlugIn(_T("Dialog"));
234     m_dFont_war = new wxFont( fo );
235     m_dFont_map = new wxFont( fo );
236 #else
237     m_dFont_map = new wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
238     m_dFont_war = new wxFont( 16, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_NORMAL );
239 
240 #endif
241 
242     if(wxGetDisplaySize().x > 0){
243 //#ifdef __WXGTK__
244 //        GdkScreen *screen = gdk_screen_get_default();
245 //        m_pixelMM = (double)gdk_screen_get_monitor_width_mm(screen, 0) / wxGetDisplaySize().x;
246 //#else
247         m_pixelMM = (double)PlugInGetDisplaySizeMM() / wxMax(wxGetDisplaySize().x, wxGetDisplaySize().y);
248 //#endif
249          m_pixelMM = wxMax(.02, m_pixelMM);          // protect against bad data
250     }
251     else
252           m_pixelMM = 0.27;               // semi-standard number...
253 
254     m_pGribTimelineRecordSet = NULL;
255     m_last_vp_scale = 0.;
256 
257     m_oDC = NULL;
258 
259     InitColorsTable();
260     for(int i=0; i<GribOverlaySettings::SETTINGS_COUNT; i++)
261         m_pOverlay[i] = NULL;
262 
263     m_ParticleMap = NULL;
264     m_tParticleTimer.Connect(wxEVT_TIMER, wxTimerEventHandler( GRIBOverlayFactory::OnParticleTimer ), NULL, this);
265     m_bUpdateParticles = false;
266 
267     // Generate the wind arrow cache
268 
269     if(m_pixelMM < 0.2)
270         windArrowSize = 5.0 / m_pixelMM;            // Target scaled arrow size
271     else
272         windArrowSize = 26;            // Standard value for desktop
273 
274     int r = 5, i=0;     // wind is very light, draw a circle
275     double s = 2 * M_PI / 10.;
276     for( double a = 0; a < 2 * M_PI; a += s )
277         m_WindArrowCache[0].pushLine(r*sin(a), r*cos(a), r*sin(a+s), r*cos(a+s));
278 
279     int dec = -windArrowSize / 2;
280     int pointerLength = windArrowSize / 3;
281 
282     // the barbed arrows
283     for(i=1; i<14; i++) {
284         LineBuffer &arrow = m_WindArrowCache[i];
285 
286         arrow.pushLine( dec, 0, dec + windArrowSize, 0 );   // hampe
287         arrow.pushLine( dec, 0, dec + pointerLength, pointerLength/2 );    // flèche
288         arrow.pushLine( dec, 0, dec + pointerLength, -(pointerLength/2) );   // flèche
289     }
290 
291     int featherPosition = windArrowSize / 6;
292 
293     int b1 = dec + windArrowSize - featherPosition;  // position de la 1ère barbule
294     int b2 = dec + windArrowSize;  // position de la 1ère barbule si >= 10 noeuds
295 
296     int lpetite = windArrowSize / 5;
297     int lgrande = lpetite * 2;
298 
299     // 5 ktn
300     m_WindArrowCache[1].pushPetiteBarbule( b1, lpetite );
301     // 10 ktn
302     m_WindArrowCache[2].pushGrandeBarbule( b2, lgrande );
303     // 15 ktn
304     m_WindArrowCache[3].pushGrandeBarbule( b2, lgrande );
305     m_WindArrowCache[3].pushPetiteBarbule( b2 - featherPosition, lpetite );
306     // 20 ktn
307     m_WindArrowCache[4].pushGrandeBarbule( b2, lgrande );
308     m_WindArrowCache[4].pushGrandeBarbule( b2 - featherPosition, lgrande );
309     // 25 ktn
310     m_WindArrowCache[5].pushGrandeBarbule( b2, lgrande );
311     m_WindArrowCache[5].pushGrandeBarbule( b2 - featherPosition, lgrande );
312     m_WindArrowCache[5].pushPetiteBarbule( b2 - featherPosition*2, lpetite );
313     // 30 ktn
314     m_WindArrowCache[6].pushGrandeBarbule( b2, lgrande );
315     m_WindArrowCache[6].pushGrandeBarbule( b2 - featherPosition, lgrande );
316     m_WindArrowCache[6].pushGrandeBarbule( b2 - featherPosition*2, lgrande );
317     // 35 ktn
318     m_WindArrowCache[7].pushGrandeBarbule( b2, lgrande );
319     m_WindArrowCache[7].pushGrandeBarbule( b2 - featherPosition, lgrande );
320     m_WindArrowCache[7].pushGrandeBarbule( b2 - featherPosition*2, lgrande );
321     m_WindArrowCache[7].pushPetiteBarbule( b2 - featherPosition*3, lpetite );
322     // 40 ktn
323     m_WindArrowCache[8].pushGrandeBarbule( b2, lgrande );
324     m_WindArrowCache[8].pushGrandeBarbule( b2 - featherPosition, lgrande );
325     m_WindArrowCache[8].pushGrandeBarbule( b2 - featherPosition*2, lgrande );
326     m_WindArrowCache[8].pushGrandeBarbule( b2 - featherPosition*3, lgrande );
327     // 50 ktn
328     m_WindArrowCache[9].pushTriangle( b1 - featherPosition, lgrande );
329     // 60 ktn
330     m_WindArrowCache[10].pushTriangle( b1 - featherPosition, lgrande );
331     m_WindArrowCache[10].pushGrandeBarbule( b1 - featherPosition*2, lgrande );
332     // 70 ktn
333     m_WindArrowCache[11].pushTriangle( b1 - featherPosition, lgrande );
334     m_WindArrowCache[11].pushGrandeBarbule( b1 - featherPosition*2, lgrande );
335     m_WindArrowCache[11].pushGrandeBarbule( b1 - featherPosition*3, lgrande );
336     // 80 ktn
337     m_WindArrowCache[12].pushTriangle( b1 - featherPosition, lgrande );
338     m_WindArrowCache[12].pushGrandeBarbule( b1 - featherPosition*2, lgrande );
339     m_WindArrowCache[12].pushGrandeBarbule( b1 - featherPosition*3, lgrande );
340     m_WindArrowCache[12].pushGrandeBarbule( b1 - featherPosition*4, lgrande );
341     // > 90 ktn
342     m_WindArrowCache[13].pushTriangle( b1 - featherPosition, lgrande );
343     m_WindArrowCache[13].pushTriangle( b1 - featherPosition*3, lgrande );
344 
345     for(i=0; i<14; i++)
346         m_WindArrowCache[i].Finalize();
347 
348     // Generate Single and Double arrow caches
349     for(int i = 0; i<2; i++) {
350         int arrowSize;
351         int dec2 = 2;
352         int dec1 = 5;
353 
354         if(i == 0){
355             if(m_pixelMM > 0.2){
356                 arrowSize = 5.0 / m_pixelMM;            // Target scaled arrow size
357                 dec1 = arrowSize / 6;                   // pointer length
358                 dec2 = arrowSize / 8;                   // space between double lines
359             }
360             else
361                 arrowSize = 26;            // Standard value for desktop
362         }
363         else
364             arrowSize = 16;
365 
366         dec = -arrowSize / 2;
367 
368         m_SingleArrow[i].pushLine( dec, 0, dec + arrowSize, 0 );
369         m_SingleArrow[i].pushLine( dec - 2, 0, dec + dec1, dec1 + 1 );    // flèche
370         m_SingleArrow[i].pushLine( dec - 2, 0, dec + dec1, -(dec1 + 1) );   // flèche
371         m_SingleArrow[i].Finalize();
372 
373         m_DoubleArrow[i].pushLine( dec, -dec2, dec + arrowSize, -dec2 );
374         m_DoubleArrow[i].pushLine( dec, dec2, dec + arrowSize, +dec2 );
375 
376         m_DoubleArrow[i].pushLine( dec - 2, 0, dec + dec1, dec1 + 1 );    // flèche
377         m_DoubleArrow[i].pushLine( dec - 2, 0, dec + dec1, -(dec1 + 1) );   // flèche
378         m_DoubleArrow[i].Finalize();
379     }
380 }
381 
~GRIBOverlayFactory()382 GRIBOverlayFactory::~GRIBOverlayFactory()
383 {
384     ClearCachedData();
385 
386     ClearParticles();
387 
388     if(m_oDC)
389         delete m_oDC;
390 
391 }
392 
Reset()393 void GRIBOverlayFactory::Reset()
394 {
395     m_pGribTimelineRecordSet = NULL;
396 
397     ClearCachedData();
398 }
399 
SetGribTimelineRecordSet(GribTimelineRecordSet * pGribTimelineRecordSet)400 void GRIBOverlayFactory::SetGribTimelineRecordSet( GribTimelineRecordSet *pGribTimelineRecordSet )
401 {
402     Reset();
403     m_pGribTimelineRecordSet = pGribTimelineRecordSet;
404 
405 }
406 
ClearCachedData(void)407 void GRIBOverlayFactory::ClearCachedData( void )
408 {
409     //    Clear out the cached bitmaps
410     for(int i=0; i<GribOverlaySettings::SETTINGS_COUNT; i++) {
411         delete m_pOverlay[i];
412         m_pOverlay[i] = NULL;
413     }
414 }
415 
416 #ifdef __OCPN__ANDROID__
417 #include "pi_shaders.h"
418 #endif
419 
RenderGLGribOverlay(wxGLContext * pcontext,PlugIn_ViewPort * vp)420 bool GRIBOverlayFactory::RenderGLGribOverlay( wxGLContext *pcontext, PlugIn_ViewPort *vp )
421 {
422     if(g_bpause)
423         return false;
424 
425     //qDebug() << "RenderGLGribOverlay" << sw.GetTime();
426 
427     if(!m_oDC)
428         m_oDC = new pi_ocpnDC();
429 
430     m_oDC->SetVP(vp);
431     m_oDC->SetDC(NULL);
432 
433     m_pdc = NULL;                  // inform lower layers that this is OpenGL render
434 
435 
436     bool rv = DoRenderGribOverlay( vp );
437 
438     //qDebug() << "RenderGLGribOverlayDone" << sw.GetTime();
439 
440 
441     return rv;
442 }
443 
RenderGribOverlay(wxDC & dc,PlugIn_ViewPort * vp)444 bool GRIBOverlayFactory::RenderGribOverlay( wxDC &dc, PlugIn_ViewPort *vp )
445 {
446     if(!m_oDC)
447         m_oDC = new pi_ocpnDC();
448 
449     m_oDC->SetVP(vp);
450     m_oDC->SetDC(&dc);
451 
452     m_pdc = NULL;
453 #if 0
454 #if wxUSE_GRAPHICS_CONTEXT
455     wxMemoryDC *pmdc;
456     pmdc = wxDynamicCast(&dc, wxMemoryDC);
457     wxGraphicsContext *pgc = wxGraphicsContext::Create( *pmdc );
458     m_gdc = pgc;
459 #endif
460     m_pdc = &dc;
461 #endif
462     bool rv = DoRenderGribOverlay( vp );
463 
464     return rv;
465 }
466 
SettingsIdToGribId(int i,int & idx,int & idy,bool & polar)467 void GRIBOverlayFactory::SettingsIdToGribId(int i, int &idx, int &idy, bool &polar)
468 {
469     idx = idy = -1;
470     polar = false;
471     switch(i) {
472     case GribOverlaySettings::WIND:
473         idx = Idx_WIND_VX + m_Altitude, idy = Idx_WIND_VY + m_Altitude; break;
474     case GribOverlaySettings::WIND_GUST:
475         if( !m_Altitude ) { idx = Idx_WIND_GUST; } break;
476     case GribOverlaySettings::PRESSURE:
477         if( !m_Altitude ) { idx = Idx_PRESSURE; } break;
478     case GribOverlaySettings::WAVE:
479         if( !m_Altitude ) { idx = Idx_HTSIGW, idy = Idx_WVDIR, polar = true; } break;
480     case GribOverlaySettings::CURRENT:
481         if( !m_Altitude ) {  idx = Idx_SEACURRENT_VX, idy = Idx_SEACURRENT_VY; } break;
482     case GribOverlaySettings::PRECIPITATION:
483         if( !m_Altitude ) { idx = Idx_PRECIP_TOT; } break;
484     case GribOverlaySettings::CLOUD:
485         if( !m_Altitude ) { idx = Idx_CLOUD_TOT; } break;
486     case GribOverlaySettings::AIR_TEMPERATURE:
487         if( !m_Altitude ) { idx = Idx_AIR_TEMP; } break;
488     case GribOverlaySettings::SEA_TEMPERATURE:
489         if( !m_Altitude ) { idx = Idx_SEA_TEMP; } break;
490     case GribOverlaySettings::CAPE:
491         if( !m_Altitude ) { idx = Idx_CAPE; } break;
492     case GribOverlaySettings::COMP_REFL:
493         if( !m_Altitude ) { idx = Idx_COMP_REFL; } break;
494     }
495 }
496 
DoRenderGribOverlay(PlugIn_ViewPort * vp)497 bool GRIBOverlayFactory::DoRenderGribOverlay( PlugIn_ViewPort *vp )
498 {
499     if( !m_pGribTimelineRecordSet ) {
500         DrawMessageWindow( ( m_Message ), vp->pix_width, vp->pix_height, m_dFont_war );
501         return false;
502     }
503 
504     // setup numbers texture if needed
505     if(!m_pdc) {
506         #ifdef __WXQT__
507         wxFont font = GetOCPNGUIScaledFont_PlugIn(_T("Dialog"));
508         #else
509         wxFont font( 9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
510         #endif
511         m_TexFontNumbers.Build(font);
512     }
513 
514     m_Message_Hiden.Empty();
515 
516     //    If the scale has changed, clear out the cached bitmaps in DC mode
517     if( m_pdc && vp->view_scale_ppm != m_last_vp_scale )
518         ClearCachedData();
519 
520     m_last_vp_scale = vp->view_scale_ppm;
521 
522     //     render each type of record
523     GribRecord **pGR = m_pGribTimelineRecordSet->m_GribRecordPtrArray;
524     wxArrayPtrVoid **pIA = m_pGribTimelineRecordSet->m_IsobarArray;
525 
526     for(int overlay = 1; overlay >= 0; overlay--) {
527         for(int i=0; i<GribOverlaySettings::SETTINGS_COUNT; i++) {
528             if(i == GribOverlaySettings::WIND ) {
529                 if(overlay) {   /* render overlays first */
530                     if(m_dlg.m_bDataPlot[i]) RenderGribOverlayMap( i, pGR, vp );
531                 } else {
532                     if(m_dlg.m_bDataPlot[i]){
533                         RenderGribBarbedArrows( i, pGR, vp );
534                         RenderGribIsobar( i, pGR, pIA, vp );
535                         RenderGribNumbers( i, pGR, vp );
536                         RenderGribParticles( i, pGR, vp );
537                     } else {
538                         if(m_Settings.Settings[i].m_iBarbedVisibility) RenderGribBarbedArrows( i, pGR, vp );
539                     }
540                 }
541                 continue;
542             }
543             if(i == GribOverlaySettings::PRESSURE ) {
544                 if(!overlay) {   /*no overalay for pressure*/
545                     if( m_dlg.m_bDataPlot[i] ) {
546                         RenderGribIsobar( i, pGR, pIA, vp );
547                         RenderGribNumbers( i, pGR, vp );
548                     } else {
549                         if(m_Settings.Settings[i].m_iIsoBarVisibility) RenderGribIsobar( i, pGR, pIA, vp );
550                     }
551                 }
552                 continue;
553             }
554             if( m_dlg.InDataPlot(i) && !m_dlg.m_bDataPlot[i] )
555                 continue;
556 
557             if(overlay) /* render overlays first */
558                 RenderGribOverlayMap( i, pGR, vp );
559             else {
560                 RenderGribBarbedArrows( i, pGR, vp );
561                 RenderGribIsobar( i, pGR, pIA, vp );
562                 RenderGribDirectionArrows( i, pGR, vp );
563                 RenderGribNumbers( i, pGR, vp );
564                 RenderGribParticles( i, pGR, vp );
565             }
566         }
567     }
568     if( m_Altitude ) {
569         if( ! m_Message_Hiden.IsEmpty() ) m_Message_Hiden.Append(_T("\n"));
570 		m_Message_Hiden.Append(_("Warning : Data at Geopotential Height")).Append(_T(" "))
571             .Append(m_Settings.GetAltitudeFromIndex(m_Altitude, m_Settings.Settings[GribOverlaySettings::PRESSURE].m_Units)).Append(_T(" "))
572             .Append(m_Settings.GetUnitSymbol(GribOverlaySettings::PRESSURE))
573             .Append(_T(" ! "));
574     }
575     if( !m_Message_Hiden.IsEmpty() ) m_Message_Hiden.Append( _T("\n") );
576     m_Message_Hiden.Append( m_Message );
577     DrawMessageWindow( m_Message_Hiden , vp->pix_width, vp->pix_height, m_dFont_map );
578     return true;
579 }
580 
581 // isClearSky checks that there is no rain or clouds at all.
isClearSky(int settings,double v)582 static inline bool isClearSky(int settings, double v) {
583     return ((settings == GribOverlaySettings::PRECIPITATION) ||
584             (settings == GribOverlaySettings::CLOUD)) && v < 0.01;
585 }
586 
587 #ifdef ocpnUSE_GL
GetCalibratedGraphicColor(int settings,double val_in,unsigned char * data)588 void GRIBOverlayFactory::GetCalibratedGraphicColor(int settings, double val_in, unsigned char *data)
589 {
590     unsigned char r, g, b, a;
591     a = m_Settings.m_iOverlayTransparency;
592 
593     if( val_in != GRIB_NOTDEF ) {
594         val_in = m_Settings.CalibrateValue(settings, val_in);
595         //set full transparency if no rain or no clouds at all
596         // TODO: make map support this
597         if (( settings == GribOverlaySettings::PRECIPITATION ||
598               settings == GribOverlaySettings::CLOUD ) && val_in < 0.01)
599             a = 0;
600 
601         GetGraphicColor(settings, val_in, r, g, b);
602     } else
603         r = 255, g = 255, b = 255, a = 0;
604 
605     /* for some reason r g b values are inverted, but not alpha,
606        this fixes it, but I would like to find the actual cause */
607 #ifndef __OCPN__ANDROID__
608     data[0] = 255-r;
609     data[1] = 255-g;
610     data[2] = 255-b;
611     data[3] = a;
612 #else
613     data[0] = r;
614     data[1] = g;
615     data[2] = b;
616     data[3] = a;
617 #endif
618 
619 }
620 
CreateGribGLTexture(GribOverlay * pGO,int settings,GribRecord * pGR)621 bool GRIBOverlayFactory::CreateGribGLTexture( GribOverlay *pGO, int settings, GribRecord *pGR)
622 {
623     bool repeat = pGR->getLonMin() == 0 && pGR->getLonMax() + pGR->getDi() >= 360.;
624 
625     // create the texture to the size of the grib data plus a transparent border
626     int tw, th, samples = 1;
627     double delta = 0;;
628     if (pGR->getNi() > 1024 || pGR->getNj() > 1024 ) {
629         // downsample
630         samples = 0;
631         tw = pGR->getNi();
632         th = pGR->getNj();
633         double dw, dh;
634         dw = (tw >  1022)?1022./tw:1.;
635         dh = (th >  1022)?1022./th:1.;
636         delta = wxMin(dw, dh);
637         th *= delta;
638         tw *= delta;
639         tw += 2*!repeat;
640         th += 2;
641     }
642     else for(;;) {
643         // oversample up to 16x
644         tw = samples*(pGR->getNi()-1)+1 + 2*!repeat;
645         th = samples*(pGR->getNj()-1)+1 + 2;
646         if(tw >= 512 || th >= 512 || samples == 16)
647             break;
648         samples *= 2;
649     }
650 
651     //    Dont try to create enormous GRIB textures
652     if( tw > 1024 || th > 1024 )
653         return false;
654 
655     pGO->m_iTexDataDim[0] = tw;
656     pGO->m_iTexDataDim[1] = th;
657 
658 #ifdef USE_ANDROID_GLES2
659     int width_pot = tw;
660     int height_pot = th;
661 
662     // If required by platform, grow the texture to next larger NPOT size.
663     //  Retain actual data size in class storage, for later render scaling
664     //if( b_pot )
665     {
666         int xp = tw;
667         if(((xp != 0) && !(xp & (xp - 1))))     // detect already exact POT
668             width_pot = xp;
669         else{
670             int a = 0;
671             while( xp ) {
672                 xp = xp >> 1;
673                 a++;
674             }
675             width_pot = 1 << a;
676         }
677 
678         xp = th;
679         if(((xp != 0) && !(xp & (xp - 1))))
680             height_pot = xp;
681         else{
682             int a = 0;
683             while( xp ) {
684                 xp = xp >> 1;
685                 a++;
686             }
687             height_pot = 1 << a;
688         }
689     }
690 
691     tw = width_pot;
692     th = height_pot;
693 #endif
694 
695     unsigned char *data = new unsigned char[tw*th*4];
696     if (samples == 0) {
697         for( int j = 0; j < pGR->getNj(); j++ ) {
698             for( int i = 0; i < pGR->getNi(); i++ ) {
699                 double v = pGR->getValue(i,   j);
700                 int y = (j + 1)*delta;
701                 int x = (i + !repeat)*delta;
702                 int doff = 4*(y*tw + x);
703                 GetCalibratedGraphicColor(settings, v, data + doff);
704             }
705         }
706     }
707     else if(samples == 1 ) { // optimized case when there is only 1 sample
708         for( int j = 0; j < pGR->getNj(); j++ ) {
709             for( int i = 0; i < pGR->getNi(); i++ ) {
710                 double v = pGR->getValue(i,   j);
711                 int y = j + 1;
712                 int x = i + !repeat;
713                 int doff = 4*(y*tw + x);
714                 GetCalibratedGraphicColor(settings, v, data + doff);
715             }
716         }
717     } else {
718         for( int j = 0; j < pGR->getNj(); j++ ) {
719             for( int i = 0; i < pGR->getNi(); i++ ) {
720                 double v00 = pGR->getValue(i,   j), v01 = GRIB_NOTDEF;
721                 double v10 = GRIB_NOTDEF, v11 = GRIB_NOTDEF;
722                 if(i < pGR->getNi()-1) {
723                     v01 = pGR->getValue(i+1, j);
724                     if(j < pGR->getNj()-1)
725                         v11 = pGR->getValue(i+1, j+1);
726                 }
727                 if(j < pGR->getNj()-1)
728                     v10 = pGR->getValue(i,   j+1);
729 
730                 for( int ys = 0; ys<samples; ys++) {
731                     int y = j * samples + ys + 1;
732                     double yd = (double)ys/samples;
733                     double v0, v1;
734                     double a0 = 1, a1 = 1;
735                     if(v10 == GRIB_NOTDEF) {
736                         v0 = v00;
737                         if(v00 == GRIB_NOTDEF)
738                             a0 = 0;
739                         else
740                             a0 = 1-yd;
741                     } else if(v00 == GRIB_NOTDEF)
742                         v0 = v10, a0 = yd;
743                     else
744                         v0 = (1-yd)*v00 + yd*v10;
745                     if(v11 == GRIB_NOTDEF) {
746                         v1 = v01;
747                         if(v01 == GRIB_NOTDEF)
748                             a1 = 0;
749                         else
750                             a1 = 1-yd;
751                     } else if(v01 == GRIB_NOTDEF)
752                         v1 = v11, a1 = yd;
753                     else
754                         v1 = (1-yd)*v01 + yd*v11;
755 
756                     for( int xs = 0; xs<samples; xs++) {
757                         int x = i * samples + xs + !repeat;
758                         double xd = (double)xs/samples;
759                         double v, a;
760                         if(v1 == GRIB_NOTDEF)
761                             v = v0, a = (1-xd)*a0;
762                         else if(v0 == GRIB_NOTDEF)
763                             v = v1, a = xd*a1;
764                         else {
765                             v = (1-xd)*v0 + xd*v1;
766                             a = (1-xd)*a0 + xd*a1;
767                         }
768 
769                         int doff = 4*(y*tw + x);
770                         GetCalibratedGraphicColor(settings, v, data + doff);
771                         data[doff+3] *= a;
772 
773                         if(i == pGR->getNi()-1)
774                             break;
775                     }
776                     if(j == pGR->getNj()-1)
777                         break;
778                 }
779             }
780         }
781     }
782 
783     /* complete borders */
784     memcpy(data              , data + 4*tw*1     , 4*tw);
785     memcpy(data + 4*tw*(th-1), data + 4*tw*(th-2), 4*tw);
786     for(int x = 0; x < tw; x++) {
787         int doff = 4*x;
788         data[doff+3] = 0;
789         doff = 4*((th-1)*tw + x);
790         data[doff+3] = 0;
791     }
792 
793     if(!repeat)
794         for(int y = 0; y < th; y++) {
795             int doff = 4*y*tw, soff = doff + 4;
796             memcpy(data + doff, data + soff, 4);
797             data[doff+3] = 0;
798             doff = 4*(y*tw + tw-1), soff = doff - 4;
799             memcpy(data + doff, data + soff, 4);
800             data[doff+3] = 0;
801         }
802 
803     GLuint texture;
804     glGenTextures(1, &texture);
805     glBindTexture(texture_format, texture);
806 
807     glTexParameteri( texture_format, GL_TEXTURE_WRAP_S, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE );
808     glTexParameteri( texture_format, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
809     glTexParameteri( texture_format, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
810     glTexParameteri( texture_format, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
811 
812 #ifndef USE_ANDROID_GLES2
813     glPushClientAttrib( GL_CLIENT_PIXEL_STORE_BIT );
814 
815     glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
816     glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0 );
817     glPixelStorei( GL_UNPACK_SKIP_ROWS, 0 );
818     glPixelStorei( GL_UNPACK_ROW_LENGTH, tw );
819 
820     glTexImage2D(texture_format, 0, GL_RGBA, tw, th,
821                  0, GL_RGBA, GL_UNSIGNED_BYTE, data);
822 
823     glPopClientAttrib();
824 #else
825     glTexImage2D(texture_format, 0, GL_RGBA, tw, th,
826                  0, GL_RGBA, GL_UNSIGNED_BYTE, data);
827 #endif
828 
829     delete [] data;
830 
831     pGO->m_iTexture = texture;
832     pGO->m_iTextureDim[0] = tw;
833     pGO->m_iTextureDim[1] = th;
834 
835     return true;
836 }
837 #endif
838 
CreateGribImage(int settings,GribRecord * pGR,PlugIn_ViewPort * vp,int grib_pixel_size,const wxPoint & porg)839 wxImage GRIBOverlayFactory::CreateGribImage( int settings, GribRecord *pGR,
840                                              PlugIn_ViewPort *vp, int grib_pixel_size,
841                                              const wxPoint &porg )
842 {
843     wxPoint pmin;
844     GetCanvasPixLL( vp, &pmin, pGR->getLatMin(), pGR->getLonMin() );
845     wxPoint pmax;
846     GetCanvasPixLL( vp, &pmax, pGR->getLatMax(), pGR->getLonMax() );
847 
848     int width = abs( pmax.x - pmin.x );
849     int height = abs( pmax.y - pmin.y );
850 
851     //    Dont try to create enormous GRIB bitmaps ( no more than the screen size )
852     if( width > m_ParentSize.GetWidth() || height > m_ParentSize.GetHeight() )
853         return wxNullImage;
854 
855     //    This could take a while....
856     wxImage gr_image( width, height );
857     gr_image.InitAlpha();
858 
859     wxPoint p;
860     for( int ipix = 0; ipix < ( width - grib_pixel_size + 1 ); ipix += grib_pixel_size ) {
861         for( int jpix = 0; jpix < ( height - grib_pixel_size + 1 ); jpix += grib_pixel_size ) {
862             double lat, lon;
863             p.x = ipix + porg.x;
864             p.y = jpix + porg.y;
865             GetCanvasLLPix( vp, p, &lat, &lon );
866 
867             double v = pGR->getInterpolatedValue(lon, lat);
868             if( v != GRIB_NOTDEF ) {
869                 v = m_Settings.CalibrateValue(settings, v);
870                 wxColour c = GetGraphicColor(settings, v);
871 
872                 //set full transparency if no rain or no clouds at all
873                 unsigned char a = isClearSky(settings, v) ? 0 : m_Settings.m_iOverlayTransparency;
874 
875                 unsigned char r = c.Red();
876                 unsigned char g = c.Green();
877                 unsigned char b = c.Blue();
878 
879                 for( int xp = 0; xp < grib_pixel_size; xp++ )
880                     for( int yp = 0; yp < grib_pixel_size; yp++ ) {
881                         gr_image.SetRGB( ipix + xp, jpix + yp, r, g, b );
882                         gr_image.SetAlpha( ipix + xp, jpix + yp, a );
883                     }
884             } else {
885                 for( int xp = 0; xp < grib_pixel_size; xp++ )
886                     for( int yp = 0; yp < grib_pixel_size; yp++ )
887                         gr_image.SetAlpha( ipix + xp, jpix + yp, 0 );
888             }
889         }
890     }
891 
892     return gr_image.Blur( 4 );
893 }
894 
895 struct ColorMap {
896     double val;
897     wxString text;
898     unsigned char r;
899     unsigned char g;
900     unsigned char b;
901 };
902 
903 static ColorMap CurrentMap[] =
904 {{0,  _T("#d90000")},  {1, _T("#d92a00")},  {2, _T("#d96e00")},  {3, _T("#d9b200")},
905  {4,  _T("#d4d404")},  {5, _T("#a6d906")},  {7, _T("#06d9a0")},  {9, _T("#00d9b0")},
906  {12, _T("#00d9c0")}, {15, _T("#00aed0")}, {18, _T("#0083e0")}, {21, _T("#0057e0")},
907  {24, _T("#0000f0")}, {27, _T("#0400f0")}, {30, _T("#1c00f0")}, {36, _T("#4800f0")},
908  {42, _T("#6900f0")}, {48, _T("#a000f0")}, {56, _T("#f000f0")}};
909 
910 static ColorMap GenericMap[] =
911 {{0, _T("#00d900")},  {1, _T("#2ad900")},  {2, _T("#6ed900")},  {3, _T("#b2d900")},
912  {4, _T("#d4d400")},  {5, _T("#d9a600")},  {7, _T("#d90000")},  {9, _T("#d90040")},
913  {12, _T("#d90060")}, {15, _T("#ae0080")}, {18, _T("#8300a0")}, {21, _T("#5700c0")},
914  {24, _T("#0000d0")}, {27, _T("#0400e0")}, {30, _T("#0800e0")}, {36, _T("#a000e0")},
915  {42, _T("#c004c0")}, {48, _T("#c008a0")}, {56, _T("#c0a008")}};
916 
917 //    HTML colors taken from zygrib representation
918 static ColorMap WindMap[] =
919 {{0, _T("#288CFF")},{3, _T("#00AFFF")},{6, _T("#00DCE1")},{9, _T("#00F7B0")},{12, _T("#00EA9C")},
920 {15, _T("#82F059")},{18, _T("#F0F503")},{21, _T("#FFED00")},{24, _T("#FFDB00")},{27, _T("#FFC700")},
921 {30, _T("#FFB400")},{33, _T("#FF9800")},{36, _T("#FF7E00")},{39, _T("#F77800")},{42, _T("#EC7814")},
922 {45, _T("#E4711E")},{48, _T("#E06128")},{51, _T("#DC5132")},{54, _T("#D5453C")},{57, _T("#CD3A46")},
923 {60, _T("#BE2C50")},{63, _T("#B41A5A")},{66, _T("#AA1464")},{70, _T("#962878")},{75, _T("#8C328C")}};
924 
925 //    HTML colors taken from zygrib representation
926 static ColorMap AirTempMap[] =
927 {{0, _T("#283282")}, {5, _T("#273c8c")}, {10, _T("#264696")}, {14, _T("#2350a0")},
928  {18, _T("#1f5aaa")}, {22, _T("#1a64b4")}, {26, _T("#136ec8")}, {29, _T("#0c78e1")},
929  {32, _T("#0382e6")}, {35, _T("#0091e6")}, {38, _T("#009ee1")}, {41 , _T("#00a6dc")},
930  {44 , _T("#00b2d7")}, {47 , _T("#00bed2")}, {50  , _T("#28c8c8")}, { 53 , _T("#78d2aa")},
931  { 56 , _T("#8cdc78")}, { 59 , _T("#a0eb5f")}, {62 , _T("#c8f550")}, {65 , _T("#f3fb02")},
932  {68 , _T("#ffed00")}, { 71, _T("#ffdd00")}, {74 , _T("#ffc900")}, {78 , _T("#ffab00")},
933  {82 , _T("#ff8100")}, { 86, _T("#f1780c")}, {90 , _T("#e26a23")}, {95 , _T("#d5453c")},
934  {100 , _T("#b53c59")}};
935 
936 static ColorMap SeaTempMap[] =
937 {{0, _T("#0000d9")},  {1, _T("#002ad9")},  {2, _T("#006ed9")},  {3, _T("#00b2d9")},
938  {4, _T("#00d4d4")},  {5, _T("#00d9a6")},  {7, _T("#00d900")},  {9, _T("#95d900")},
939  {12, _T("#d9d900")}, {15, _T("#d9ae00")}, {18, _T("#d98300")}, {21, _T("#d95700")},
940  {24, _T("#d90000")}, {27, _T("#ae0000")}, {30, _T("#8c0000")}, {36, _T("#870000")},
941  {42, _T("#690000")}, {48, _T("#550000")}, {56, _T("#410000")}};
942 
943     //    HTML colors taken from ZyGrib representation
944 static ColorMap PrecipitationMap[] =
945 {{0,   _T("#ffffff")}, {.01, _T("#c8f0ff")}, {.02, _T("#b4e6ff")}, {.05, _T("#8cd3ff")},
946  {.07, _T("#78caff")}, {.1 , _T("#6ec1ff")}, {.2 , _T("#64b8ff")}, {.5 , _T("#50a6ff")},
947  {.7 , _T("#469eff")}, {1.0, _T("#3c96ff")}, {2.0, _T("#328eff")}, {5.0, _T("#1e7eff")},
948  {7.0, _T("#1476f0")}, {10 , _T("#0a6edc")}, {20 , _T("#0064c8")}, {50, _T("#0052aa")}};
949 
950     //    HTML colors taken from ZyGrib representation
951 static ColorMap CloudMap[] =
952 {{0,  _T("#ffffff")}, {1,  _T("#f0f0e6")}, {10, _T("#e6e6dc")}, {20, _T("#dcdcd2")},
953  {30, _T("#c8c8b4")}, {40, _T("#aaaa8c")}, {50, _T("#969678")}, {60, _T("#787864")},
954  {70, _T("#646450")}, {80, _T("#5a5a46")}, {90, _T("#505036")}};
955 
956 static ColorMap CAPEMap[] =
957 { { 0, _T("#0046c8") }, { 5, _T("#0050f0") },{ 10, _T("#005aff") },{ 15, _T("#0069ff") },
958 { 20, _T("#0078ff") },{ 30, _T("#000cff") },{ 45, _T("#00a1ff") },{ 60, _T("#00b6fa") },
959 { 100, _T("#00c9ee") },{ 150, _T("#00e0da") },{ 200, _T("#00e6b4") },{ 300, _T("#82e678") },
960 { 500, _T("#9bff3b") },{ 700, _T("#ffdc00") },{ 1000, _T("#ffb700") }, { 1500, _T("#f37800") },
961 { 2000, _T("#d4440c") },{ 2500, _T("#c8201c") },{ 3000, _T("#ad0430") },
962 };
963 
964 #if 0
965 static ColorMap *ColorMaps[] = {CurrentMap, GenericMap, WindMap, AirTempMap, SeaTempMap, PrecipitationMap, CloudMap};
966 #endif
967 
968 enum {
969     GENERIC_GRAPHIC_INDEX, WIND_GRAPHIC_INDEX, AIRTEMP__GRAPHIC_INDEX, SEATEMP_GRAPHIC_INDEX,
970     PRECIPITATION_GRAPHIC_INDEX, CLOUD_GRAPHIC_INDEX, CURRENT_GRAPHIC_INDEX, CAPE_GRAPHIC_INDEX
971 };
972 
InitColor(ColorMap * map,size_t maplen)973 static void InitColor(ColorMap *map, size_t maplen)
974 {
975     wxColour c;
976     for (size_t i = 0; i < maplen; i++) {
977         c.Set(map[i].text);
978         map[i].r = c.Red();
979         map[i].g = c.Green();
980         map[i].b = c.Blue();
981     }
982 }
983 
InitColorsTable()984 void GRIBOverlayFactory::InitColorsTable( )
985 {
986     InitColor(CurrentMap, (sizeof CurrentMap) / (sizeof *CurrentMap));
987     InitColor(GenericMap, (sizeof GenericMap) / (sizeof *GenericMap));
988     InitColor(WindMap, (sizeof WindMap) / (sizeof *WindMap));
989     InitColor(AirTempMap, (sizeof AirTempMap) / (sizeof *AirTempMap));
990     InitColor(SeaTempMap, (sizeof SeaTempMap) / (sizeof *SeaTempMap));
991     InitColor(PrecipitationMap, (sizeof PrecipitationMap) / (sizeof *PrecipitationMap));
992     InitColor(CloudMap, (sizeof CloudMap) / (sizeof *CloudMap));
993 	InitColor(CAPEMap, (sizeof CAPEMap) / (sizeof *CAPEMap));
994 }
995 
GetGraphicColor(int settings,double val_in,unsigned char & r,unsigned char & g,unsigned char & b)996 void GRIBOverlayFactory::GetGraphicColor(int settings, double val_in, unsigned char &r, unsigned char &g, unsigned char &b)
997 {
998     int colormap_index = m_Settings.Settings[settings].m_iOverlayMapColors;
999     ColorMap *map;
1000     int maplen;
1001 
1002     /* normalize input value */
1003     double min = m_Settings.GetMin(settings), max = m_Settings.GetMax(settings);
1004 
1005     val_in -= min;
1006     val_in /= max-min;
1007 
1008     switch(colormap_index) {
1009     case CURRENT_GRAPHIC_INDEX:
1010         map = CurrentMap;
1011         maplen = (sizeof CurrentMap) / (sizeof *CurrentMap);
1012         break;
1013     case GENERIC_GRAPHIC_INDEX:
1014         map = GenericMap;
1015         maplen = (sizeof GenericMap) / (sizeof *GenericMap);
1016         break;
1017     case WIND_GRAPHIC_INDEX:
1018         map = WindMap;
1019         maplen = (sizeof WindMap) / (sizeof *WindMap);
1020         break;
1021     case AIRTEMP__GRAPHIC_INDEX:
1022         map = AirTempMap;
1023         maplen = (sizeof AirTempMap) / (sizeof *AirTempMap);
1024         break;
1025     case SEATEMP_GRAPHIC_INDEX:
1026         map = SeaTempMap;
1027         maplen = (sizeof SeaTempMap) / (sizeof *SeaTempMap);
1028         break;
1029     case PRECIPITATION_GRAPHIC_INDEX:
1030         map = PrecipitationMap;
1031         maplen = (sizeof PrecipitationMap) / (sizeof *PrecipitationMap);
1032         break;
1033     case CLOUD_GRAPHIC_INDEX:
1034         map = CloudMap;
1035         maplen = (sizeof CloudMap) / (sizeof *CloudMap);
1036         break;
1037 	case CAPE_GRAPHIC_INDEX:
1038 		map = CAPEMap;
1039 		maplen = (sizeof CAPEMap) / (sizeof *CAPEMap);
1040 		break;
1041     default:
1042         return;
1043     }
1044 
1045     /* normalize map from 0 to 1 */
1046     double cmax = map[maplen-1].val;
1047 
1048     for(int i=1; i<maplen; i++) {
1049         double nmapvala = map[i-1].val/cmax;
1050         double nmapvalb = map[i].val/cmax;
1051         if(nmapvalb > val_in || i==maplen-1) {
1052             if(m_bGradualColors) {
1053                 double d = (val_in-nmapvala)/(nmapvalb-nmapvala);
1054                 r = (1-d)* map[i -1].r  + d* map[i].r;
1055                 g = (1-d)* map[i -1].g + d* map[i].g;
1056                 b = (1-d)* map[i -1].b  + d* map[i].b;
1057             } else {
1058                 r = map[i].r;
1059                 g = map[i].g;
1060                 b = map[i].b;
1061             }
1062             return;
1063         }
1064     }
1065     /* unreachable */
1066 }
1067 
GetGraphicColor(int settings,double val_in)1068 wxColour GRIBOverlayFactory::GetGraphicColor(int settings, double val_in)
1069 {
1070     unsigned char r, g, b;
1071     GetGraphicColor(settings, val_in, r, g, b);
1072     return wxColour(r, g, b);
1073 }
1074 
getLabelString(double value,int settings)1075 wxString GRIBOverlayFactory::getLabelString(double value, int settings)
1076 {
1077     int p;
1078     wxString f = _T("%.*f");
1079 
1080     switch(settings) {
1081     case GribOverlaySettings::PRESSURE: /* 2 */
1082         p = 0;
1083         if (m_Settings.Settings[settings].m_Units == 2 )
1084             p = 2;
1085         else if (m_Settings.Settings[settings].m_Units == 0 &&
1086                  m_Settings.Settings[settings].m_bAbbrIsoBarsNumbers)
1087         {
1088             value -= floor(value/100.)*100.;
1089             f = _T("%02.*f");
1090         }
1091         break;
1092     case GribOverlaySettings::WAVE: /* 3 */
1093     case GribOverlaySettings::CURRENT: /* 4 */
1094     case GribOverlaySettings::AIR_TEMPERATURE: /* 7 */
1095     case GribOverlaySettings::SEA_TEMPERATURE: /* 8 */
1096         p = 1;
1097         break;
1098     case GribOverlaySettings::PRECIPITATION: /* 5 */
1099         p = value < 100. ? 2 : value < 10. ? 1 : 0;
1100         p += m_Settings.Settings[settings].m_Units == 1 ? 1 : 0;
1101             break;
1102     default :
1103         p = 0;
1104     }
1105     return wxString::Format( f , p, value );
1106 }
1107 
1108 /* return cached wxImage for a given number, or create it if not in the cache */
getLabel(double value,int settings,wxColour back_color)1109 wxImage &GRIBOverlayFactory::getLabel(double value, int settings, wxColour back_color )
1110 {
1111     std::map <double, wxImage >::iterator it;
1112     it = m_labelCache.find(value);
1113     if (it != m_labelCache.end())
1114         return m_labelCache[value];
1115 
1116     wxString labels = getLabelString(value, settings);
1117 
1118     wxColour text_color;
1119     GetGlobalColor( _T ( "UBLCK" ), &text_color );
1120     wxPen penText(text_color);
1121 
1122     wxBrush backBrush(back_color);
1123 
1124     wxFont mfont( 9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
1125 
1126     wxScreenDC sdc;
1127     int w, h;
1128     sdc.GetTextExtent(labels, &w, &h, NULL, NULL, &mfont);
1129 
1130     int label_offset = 5;
1131 
1132     wxBitmap bm(w +  label_offset*2, h + 2);
1133     wxMemoryDC mdc(bm);
1134     mdc.Clear();
1135 
1136     mdc.SetFont( mfont );
1137     mdc.SetPen(penText);
1138     mdc.SetBrush(backBrush);
1139     mdc.SetTextForeground(text_color);
1140     mdc.SetTextBackground(back_color);
1141 
1142     int xd = 0;
1143     int yd = 0;
1144 //    mdc.DrawRoundedRectangle(xd, yd, w+(label_offset * 2), h+2, -.25);
1145     mdc.DrawRectangle(xd, yd, w+(label_offset * 2), h+2);
1146     mdc.DrawText(labels, label_offset + xd, yd+1);
1147 
1148     mdc.SelectObject(wxNullBitmap);
1149 
1150     m_labelCache[value] = bm.ConvertToImage();
1151 
1152     m_labelCache[value].InitAlpha();
1153 
1154     return m_labelCache[value];
1155 }
1156 
square(double x)1157 double square(double x) { return x*x; }
1158 
RenderGribBarbedArrows(int settings,GribRecord ** pGR,PlugIn_ViewPort * vp)1159 void GRIBOverlayFactory::RenderGribBarbedArrows( int settings, GribRecord **pGR,
1160                                                  PlugIn_ViewPort *vp )
1161 {
1162     if(!m_Settings.Settings[settings].m_bBarbedArrows)
1163         return;
1164 
1165     //  Need two records to draw the barbed arrows
1166     GribRecord *pGRX, *pGRY;
1167     int idx, idy;
1168     bool polar;
1169     SettingsIdToGribId(settings, idx, idy, polar);
1170     if(idx < 0 || idy < 0)
1171         return;
1172 
1173     pGRX = pGR[idx];
1174     pGRY = pGR[idy];
1175 
1176     if(!pGRX || !pGRY)
1177         return;
1178 
1179     wxColour colour;
1180     GetGlobalColor( _T ( "YELO2" ), &colour );
1181 
1182 #ifdef ocpnUSE_GL
1183     if( !m_pdc ) {
1184 
1185 #ifndef __OCPN__ANDROID__
1186         //      Enable anti-aliased lines, at best quality
1187             glEnable( GL_LINE_SMOOTH );
1188             glEnable( GL_BLEND );
1189             glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
1190             glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
1191             glLineWidth( 2 );
1192 #else
1193             glLineWidth( 5 );                       // 5 pixels for dense displays
1194 #endif
1195 
1196         glEnableClientState(GL_VERTEX_ARRAY);
1197     }
1198 #endif
1199 
1200     if( m_Settings.Settings[settings].m_bBarbArrFixSpac ) {
1201 
1202         //set spacing between arrows
1203         int space = adjustSpacing( m_Settings.Settings[settings].m_iBarbArrSpacing);
1204 
1205         PlugIn_ViewPort uvp = *vp;
1206         uvp.rotation = uvp.skew = 0;
1207 
1208         int arrowSize = 16;
1209 
1210         for( int i = 0; i < m_ParentSize.GetWidth(); i+= (space + arrowSize) ) {
1211             for( int j = 0; j < m_ParentSize.GetHeight(); j+= (space + arrowSize) ) {
1212                 double lat, lon;
1213                 GetCanvasLLPix( vp, wxPoint( i, j ), &lat, &lon );
1214 
1215                 double vkn, ang;
1216                 if( GribRecord::getInterpolatedValues(vkn, ang, pGRX, pGRY, lon, lat) )
1217                     drawWindArrowWithBarbs( settings, i, j, vkn * 3.6/1.852, (ang-90) * M_PI/180, ( lat < 0. ), colour, vp->rotation );
1218             }
1219         }
1220     } else {
1221 
1222         //set minimum spacing between arrows
1223         double minspace = wxMax( m_Settings.Settings[settings].m_iBarbArrSpacing, windArrowSize * 1.2 );
1224         double minspace2 = square(minspace);
1225 
1226         //    Get the the grid
1227         int imax = pGRX->getNi();                  // Longitude
1228         int jmax = pGRX->getNj();                  // Latitude
1229 
1230         wxPoint firstpx(-1000, -1000);
1231         wxPoint oldpx(-1000, -1000);
1232         wxPoint oldpy(-1000, -1000);
1233 
1234         for( int i = 0; i < imax; i++ ) {
1235             double lonl, latl;
1236 
1237             /* at midpoint of grib so as to avoid problems in projection on
1238                gribs that go all the way to the north or south pole */
1239             pGRX->getXY( i, pGRX->getNj()/2, &lonl, &latl);
1240             wxPoint pl;
1241             GetCanvasPixLL( vp, &pl, latl, lonl );
1242 
1243             if (pl.x <= firstpx.x && square( pl.x - firstpx.x) + square(pl.y - firstpx.y) < minspace2/1.44)
1244                 continue;
1245 
1246             if( square( pl.x - oldpx.x) + square( pl.y - oldpx.y ) < minspace2 )
1247                 continue;
1248 
1249             oldpx = pl;
1250             if (i == 0)
1251                 firstpx = pl;
1252 
1253             double lon = lonl;
1254             for( int j = 0; j < jmax; j++ ) {
1255                 double lat = pGRX->getY( j );
1256 
1257                 if( !PointInLLBox( vp, lon, lat ) )
1258                     continue;
1259 
1260                 wxPoint p;
1261                 GetCanvasPixLL( vp, &p, lat, lon );
1262 
1263                 if( square( p.x - oldpy.x) + square(p.y - oldpy.y ) < minspace2 )
1264                     continue;
1265 
1266                 oldpy = p;
1267 
1268                 if(lon > 180)
1269                     lon -= 360;
1270 
1271                 double vx =  pGRX->getValue( i, j );
1272                 double vy =  pGRY->getValue( i, j );
1273 
1274                 if( vx != GRIB_NOTDEF && vy != GRIB_NOTDEF ) {
1275                     double vkn, ang;
1276                     vkn = sqrt( vx * vx + vy * vy );
1277                     ang = atan2( vy, -vx );
1278                     drawWindArrowWithBarbs( settings, p.x, p.y, vkn * 3.6/1.852, ang, ( lat < 0. ), colour, vp->rotation );
1279                 }
1280             }
1281         }
1282     }
1283 
1284 
1285 #ifdef ocpnUSE_GL
1286     if( !m_pdc )
1287         glDisableClientState(GL_VERTEX_ARRAY);
1288 #endif
1289 }
1290 
RenderGribIsobar(int settings,GribRecord ** pGR,wxArrayPtrVoid ** pIsobarArray,PlugIn_ViewPort * vp)1291 void GRIBOverlayFactory::RenderGribIsobar( int settings, GribRecord **pGR,
1292                                            wxArrayPtrVoid **pIsobarArray, PlugIn_ViewPort *vp )
1293 {
1294     if(!m_Settings.Settings[settings].m_bIsoBars)
1295         return;
1296 
1297     //  Need magnitude to draw isobars
1298     int idx, idy;
1299     bool polar;
1300     SettingsIdToGribId(settings, idx, idy, polar);
1301     if(idx < 0)
1302         return;
1303 
1304     GribRecord *pGRA = pGR[idx], *pGRM = NULL;
1305 
1306     if(!pGRA)
1307         return;
1308 
1309     wxColour back_color;
1310     GetGlobalColor( _T ( "DILG1" ), &back_color );
1311 
1312     //    Initialize the array of Isobars if necessary
1313     if( !pIsobarArray[idx] ) {
1314         // build magnitude from multiple record types like wind and current
1315         if(idy >= 0 && !polar && pGR[idy]) {
1316             pGRM = GribRecord::MagnitudeRecord(*pGR[idx], *pGR[idy]);
1317             if (!pGRM->isOk()) {
1318                 m_Message_Hiden.Append(_("IsoBar Unable to compute record magnitude"));
1319                 delete pGRM;
1320                 return;
1321             }
1322             pGRA = pGRM;
1323         }
1324 
1325         pIsobarArray[idx] = new wxArrayPtrVoid;
1326         IsoLine *piso;
1327 
1328         wxGenericProgressDialog *progressdialog = nullptr;
1329         wxDateTime start = wxDateTime::Now();
1330 
1331         double min = m_Settings.GetMin(settings);
1332         double max = m_Settings.GetMax(settings);
1333 
1334         /* convert min and max to units being used */
1335         double factor = ( settings == GribOverlaySettings::PRESSURE &&
1336                             m_Settings.Settings[settings].m_Units == 2 ) ? 0.03 : 1.;//divide spacing by 1/33 for PRESURRE & inHG
1337 
1338         for( double press = min; press <= max; press += (m_Settings.Settings[settings].m_iIsoBarSpacing * factor) ) {
1339             if(progressdialog)
1340                 progressdialog->Update(press-min);
1341             else {
1342                 wxDateTime now = wxDateTime::Now();
1343                 if((now-start).GetSeconds() > 3 && press-min < (max-min)/2) {
1344                     progressdialog = new wxGenericProgressDialog(
1345                         _("Building Isobar map"), _("Wind"), max-min+1, NULL,
1346                         wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME);
1347                 }
1348             }
1349 
1350             piso = new IsoLine( press,
1351                                 m_Settings.CalibrationFactor(settings, press, true),
1352                                 m_Settings.CalibrationOffset(settings), pGRA );
1353 
1354             pIsobarArray[idx]->Add( piso );
1355         }
1356         delete progressdialog;
1357 
1358         delete pGRM;
1359     }
1360 
1361     //    Draw the Isobars
1362     for( unsigned int i = 0; i < pIsobarArray[idx]->GetCount(); i++ ) {
1363         IsoLine *piso = (IsoLine *) pIsobarArray[idx]->Item( i );
1364         piso->drawIsoLine( this, m_pdc, vp, true); //g_bGRIBUseHiDef
1365 
1366         // Draw Isobar labels
1367 
1368         int density = 40;
1369         int first = 0;
1370         if(m_pdc)
1371             piso->drawIsoLineLabels( this, m_pdc, vp, density,
1372                                      first, getLabel(piso->getValue(), settings, back_color) );
1373         else
1374             piso->drawIsoLineLabelsGL( this, vp, density,
1375                                        first, getLabelString(piso->getValue(), settings),
1376                                        back_color, m_TexFontNumbers );
1377     }
1378 }
1379 
FillGrid(GribRecord * pGR)1380 void GRIBOverlayFactory::FillGrid(GribRecord *pGR)
1381 {
1382     //    Get the the grid
1383     int imax = pGR->getNi();                  // Longitude
1384     int jmax = pGR->getNj();                  // Latitude
1385 
1386 
1387     for( int i = 0; i < imax; i++ ) {
1388         for( int j = 1; j < jmax-1; j++ ) {
1389             if(pGR->getValue(i, j) == GRIB_NOTDEF){
1390                 double acc = 0;
1391                 double div = 0;
1392                 if(pGR->getValue(i, j-1) != GRIB_NOTDEF){
1393                     acc += pGR->getValue(i, j-1);
1394                     div += 1;
1395                 }
1396                 if(pGR->getValue(i, j+1) != GRIB_NOTDEF){
1397                     acc += pGR->getValue(i, j+1);
1398                     div += 1;
1399                 }
1400                 if(div > 1)
1401                     pGR->setValue(i,j, acc / div);
1402             }
1403         }
1404     }
1405 
1406     for( int j = 0; j < jmax; j++ ) {
1407         for( int i = 1; i < imax-1; i++ ) {
1408             if(pGR->getValue(i, j) == GRIB_NOTDEF){
1409                 double acc = 0;
1410                 double div = 0;
1411                 if(pGR->getValue(i-1, j) != GRIB_NOTDEF){
1412                     acc += pGR->getValue(i-1, j);
1413                     div += 1;
1414                 }
1415                 if(pGR->getValue(i+1, j) != GRIB_NOTDEF){
1416                     acc += pGR->getValue(i+1, j);
1417                     div += 1;
1418                 }
1419                 if(div > 1)
1420                     pGR->setValue(i,j, acc / div);
1421             }
1422         }
1423     }
1424 
1425     pGR->setFilled(true);
1426 }
1427 
1428 
1429 
1430 
1431 
RenderGribDirectionArrows(int settings,GribRecord ** pGR,PlugIn_ViewPort * vp)1432 void GRIBOverlayFactory::RenderGribDirectionArrows( int settings, GribRecord **pGR,
1433                                                     PlugIn_ViewPort *vp )
1434 {
1435     if(!m_Settings.Settings[settings].m_bDirectionArrows)
1436         return;
1437     //   need two records or a polar record to draw arrows
1438     GribRecord *pGRX, *pGRY;
1439     int idx, idy;
1440     bool polar;
1441     SettingsIdToGribId(settings, idx, idy, polar);
1442     if(idx < 0 || idy < 0)
1443         return;
1444 
1445     pGRX = pGR[idx];
1446     pGRY = pGR[idy];
1447     if(!pGRX || !pGRY)
1448         return;
1449     if(!pGRX->isFilled())
1450         FillGrid(pGRX);
1451     if(!pGRY->isFilled())
1452         FillGrid(pGRY);
1453 
1454     // Set arrows Size
1455     int arrowWidth = 2;
1456     int arrowSize, arrowSizeIdx = m_Settings.Settings[settings].m_iDirectionArrowSize;
1457     if(arrowSizeIdx == 0){
1458         if(m_pixelMM > 0.2)
1459             arrowSize = 26;
1460         else
1461             arrowSize = 5. / m_pixelMM;
1462     }
1463     else
1464         arrowSize = 16;
1465 
1466     //set default colour
1467     wxColour colour;
1468     GetGlobalColor( _T ( "DILG3" ), &colour );
1469 
1470 #ifdef ocpnUSE_GL
1471     if( !m_pdc ) {
1472         if(m_pixelMM > 0.2){
1473         //      Enable anti-aliased lines, at best quality
1474             glEnable( GL_LINE_SMOOTH );
1475             glEnable( GL_BLEND );
1476             glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
1477             glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
1478         }
1479         else{
1480             if(m_Settings.Settings[settings].m_iDirectionArrowForm == 0)            // Single?
1481                 arrowWidth = 4;
1482             else
1483                 arrowWidth = 3;
1484         }
1485 
1486         glEnableClientState(GL_VERTEX_ARRAY);
1487     }
1488 #endif
1489 
1490     if( m_Settings.Settings[settings].m_bDirArrFixSpac ) {						//fixed spacing
1491 
1492         //Set spacing between arrows
1493         int space = adjustSpacing( m_Settings.Settings[settings].m_iDirArrSpacing );
1494 
1495         for( int i = 0; i < m_ParentSize.GetWidth(); i+= (space + arrowSize) ) {
1496             for( int j = 0; j < m_ParentSize.GetHeight(); j+= (space + arrowSize) ) {
1497                 double lat, lon, sh, dir;
1498                 double scale = 1.0;
1499                 GetCanvasLLPix( vp, wxPoint( i, j ), &lat, &lon );
1500 
1501                 if(polar) {  // wave arrows
1502                     sh = pGRX->getInterpolatedValue( lon, lat, true );
1503                     dir = pGRY->getInterpolatedValue( lon, lat, true, true );
1504 
1505                     if( dir == GRIB_NOTDEF || sh == GRIB_NOTDEF ) continue;
1506 
1507                 } else {	     // current arrows
1508                     if( !GribRecord::getInterpolatedValues(sh, dir, pGRX, pGRY, lon, lat) )
1509                         continue;
1510                     scale = wxMax(1.0, sh);             // Size depends on magnitude.
1511                 }
1512 
1513                 dir = (dir - 90) * M_PI / 180.;
1514 
1515                 //draw arrows
1516                 if(m_Settings.Settings[settings].m_iDirectionArrowForm == 0)
1517                     drawSingleArrow( i, j, dir + vp->rotation, colour, arrowWidth, arrowSizeIdx, scale );
1518                 else if( m_Settings.Settings[settings].m_iDirectionArrowForm == 1 )
1519                     drawDoubleArrow( i, j, dir + vp->rotation, colour, arrowWidth, arrowSizeIdx, scale );
1520                 else
1521                     drawSingleArrow( i, j, dir + vp->rotation, colour, wxMax( 1, wxMin( 8, (int)(sh+0.5) ) ), arrowSizeIdx, scale );
1522             }
1523         }
1524 
1525     } else {																//end fixed spacing -> minimum spacing
1526 
1527         //set minimum spacing between arrows
1528         double minspace = wxMax( m_Settings.Settings[settings].m_iDirArrSpacing, m_Settings.Settings[settings].m_iDirectionArrowSize * 1.2 );
1529         double minspace2 = square(minspace);
1530 
1531         //    Get the the grid
1532         int imax = pGRX->getNi();                  // Longitude
1533         int jmax = pGRX->getNj();                  // Latitude
1534 
1535         wxPoint firstpx(-1000, -1000);
1536         wxPoint oldpx(-1000, -1000);
1537         wxPoint oldpy(-1000, -1000);
1538 
1539         for( int i = 0; i < imax; i++ ) {
1540             double lonl,latl;
1541             pGRX->getXY( i, pGRX->getNj()/2, &lonl, &latl);
1542 
1543             wxPoint pl;
1544             GetCanvasPixLL( vp, &pl, latl, lonl );
1545 
1546             if (pl.x <= firstpx.x && square( pl.x - firstpx.x) + square(pl.y - firstpx.y ) < minspace2/1.44)
1547                 continue;
1548 
1549             if( square( pl.x - oldpx.x ) + square(pl.y - oldpx.y ) < minspace2)
1550                 continue;
1551 
1552             oldpx = pl;
1553             if (i == 0)
1554                 firstpx = pl;
1555 
1556             for( int j = 0; j < jmax; j++ ) {
1557                 double lon,  lat;
1558                 pGRX->getXY( i,j, &lon, &lat );
1559 
1560                 wxPoint p;
1561                 GetCanvasPixLL( vp, &p, lat, lon );
1562 
1563                 if( square( p.x - oldpy.x) + square(p.y - oldpy.y ) >= minspace2 ) {
1564                     oldpy = p;
1565 
1566                     if(lon > 180)
1567                         lon -= 360;
1568 
1569                     if( PointInLLBox( vp, lon, lat ) ) {
1570                         double sh, dir, wdh;
1571                         double scale = 1.0;
1572                         if(polar) {														//wave arrows
1573                             dir = pGRY->getValue( i, j );
1574                             sh = pGRX->getValue( i, j );
1575 
1576                             if( dir == GRIB_NOTDEF || sh == GRIB_NOTDEF ) continue;
1577 
1578                             wdh = sh+0.5;
1579                         } else {
1580                             if( !GribRecord::getInterpolatedValues(sh, dir, pGRX, pGRY, lon, lat, false) )
1581                                 continue;
1582 
1583                             wdh = (8/2.5*sh)+0.5;
1584                             scale = wxMax(1.0, sh);             // Size depends on magnitude.
1585                         }
1586 
1587                         dir = (dir - 90) * M_PI / 180.;
1588 
1589                         //draw arrows
1590                         if(m_Settings.Settings[settings].m_iDirectionArrowForm == 0)
1591                             drawSingleArrow( p.x, p.y, dir + vp->rotation, colour, arrowWidth, arrowSizeIdx, scale );
1592                         else if( m_Settings.Settings[settings].m_iDirectionArrowForm == 1 )
1593                             drawDoubleArrow( p.x, p.y, dir + vp->rotation, colour, arrowWidth, arrowSizeIdx, scale );
1594                         else
1595                             drawSingleArrow( p.x, p.y, dir + vp->rotation, colour, wxMax( 1, wxMin( 8, (int)wdh ) ), arrowSizeIdx, scale );
1596                     }
1597                 }
1598             }
1599         }
1600     }
1601 
1602 #ifdef ocpnUSE_GL
1603     if( !m_pdc )
1604         glDisableClientState(GL_VERTEX_ARRAY);
1605 #endif
1606 }
1607 
RenderGribOverlayMap(int settings,GribRecord ** pGR,PlugIn_ViewPort * vp)1608 void GRIBOverlayFactory::RenderGribOverlayMap( int settings, GribRecord **pGR, PlugIn_ViewPort *vp)
1609 {
1610     if(!m_Settings.Settings[settings].m_bOverlayMap)
1611         return;
1612 
1613     const int grib_pixel_size = 4;
1614     bool polar;
1615     int idx, idy;
1616     SettingsIdToGribId(settings, idx, idy, polar);
1617     if(idx < 0 || !pGR[idx])
1618         return;
1619 
1620     GribRecord *pGRA = pGR[idx], *pGRM = NULL;
1621     if(!pGRA)
1622         return;
1623 
1624     if(idy >= 0 && !polar && pGR[idy]) {
1625         pGRM = GribRecord::MagnitudeRecord(*pGR[idx], *pGR[idy]);
1626         if (!pGRM->isOk()) {
1627             m_Message_Hiden.Append(_("OverlayMap Unable to compute record magnitude"));
1628             delete pGRM;
1629             return;
1630         }
1631         pGRA = pGRM;
1632     }
1633 
1634     if(!pGRA->isFilled())
1635         FillGrid(pGRA);
1636 
1637 
1638     wxPoint porg;
1639     GetCanvasPixLL( vp, &porg, pGRA->getLatMax(), pGRA->getLonMin() );
1640 
1641     //    Check two BBoxes....
1642     //    TODO Make a better Intersect method
1643     bool bdraw = false;
1644     if( Intersect( vp, pGRA->getLatMin(), pGRA->getLatMax(),
1645                    pGRA->getLonMin(), pGRA->getLonMax(),
1646                    0. ) != _GOUT ) bdraw = true;
1647     if( Intersect( vp, pGRA->getLatMin(), pGRA->getLatMax(),
1648                    pGRA->getLonMin() - 360., pGRA->getLonMax() - 360.,
1649                    0. ) != _GOUT ) bdraw = true;
1650 
1651     if( bdraw ) {
1652         // If needed, create the overlay
1653         if( !m_pOverlay[settings] )
1654             m_pOverlay[settings] = new GribOverlay;
1655 
1656         GribOverlay *pGO = m_pOverlay[settings];
1657 
1658         if( !m_pdc )       //OpenGL mode
1659         {
1660 #ifdef ocpnUSE_GL
1661 
1662             texture_format = GL_TEXTURE_2D;
1663 
1664             if(!texture_format) // it's very unlikely to not have any of the above extensions
1665                 m_Message_Hiden.Append(_("Overlays not supported by this graphics hardware (Disable OpenGL)"));
1666             else {
1667                 if( !pGO->m_iTexture )
1668                     CreateGribGLTexture( pGO, settings, pGRA );
1669 
1670                 if( pGO->m_iTexture )
1671                     DrawGLTexture( pGO, pGRA, vp );
1672                 else
1673                     m_Message_Hiden.IsEmpty()?
1674                         m_Message_Hiden.Append(_("Overlays too wide and can't be displayed:"))
1675                         .Append(_T(" ")).Append(GribOverlaySettings::NameFromIndex(settings))
1676                         : m_Message_Hiden.Append(_T(",")).Append(GribOverlaySettings::NameFromIndex(settings));
1677             }
1678 #endif
1679         }
1680         else        //DC mode
1681         {
1682             if(fabs(vp->rotation) > 0.1){
1683                 m_Message_Hiden.Append(_("overlays suppressed if not north-up in DC mode (enable OpenGL)"));
1684             }
1685             else {
1686                 if( !pGO->m_pDCBitmap ) {
1687                     wxImage bl_image = CreateGribImage( settings, pGRA, vp, grib_pixel_size, porg );
1688                     if( bl_image.IsOk() ) {
1689                     //    Create a Bitmap
1690                         pGO->m_pDCBitmap = new wxBitmap( bl_image );
1691                         wxMask *gr_mask = new wxMask( *( pGO->m_pDCBitmap ), wxColour( 0, 0, 0 ) );
1692                         pGO->m_pDCBitmap->SetMask( gr_mask );
1693                     }
1694                 }
1695 
1696                 if( pGO->m_pDCBitmap )
1697                     m_pdc->DrawBitmap( *( pGO->m_pDCBitmap ), porg.x, porg.y, true );
1698                 else
1699                     m_Message_Hiden.IsEmpty()?
1700                         m_Message_Hiden.Append(_("Please Zoom or Scale Out to view invisible overlays:"))
1701                         .Append(_T(" ")).Append(GribOverlaySettings::NameFromIndex(settings))
1702                         : m_Message_Hiden.Append(_T(",")).Append(GribOverlaySettings::NameFromIndex(settings));
1703             }
1704         }
1705     }
1706 
1707     delete pGRM;
1708 }
1709 
RenderGribNumbers(int settings,GribRecord ** pGR,PlugIn_ViewPort * vp)1710 void GRIBOverlayFactory::RenderGribNumbers( int settings, GribRecord **pGR, PlugIn_ViewPort *vp )
1711 {
1712     if(!m_Settings.Settings[settings].m_bNumbers)
1713         return;
1714 
1715     //  Need magnitude to draw numbers
1716     int idx, idy;
1717     bool polar;
1718     SettingsIdToGribId(settings, idx, idy, polar);
1719     if(idx < 0)
1720         return;
1721 
1722     GribRecord *pGRA = pGR[idx], *pGRM = NULL;
1723 
1724     if(!pGRA)
1725         return;
1726 
1727     /* build magnitude from multiple record types like wind and current */
1728     if(idy >= 0 && !polar && pGR[idy]) {
1729         pGRM = GribRecord::MagnitudeRecord(*pGR[idx], *pGR[idy]);
1730         if (!pGRM->isOk()) {
1731             m_Message_Hiden.Append(_("GribNumbers Unable to compute record magnitude"));
1732             delete pGRM;
1733             return;
1734         }
1735         pGRA = pGRM;
1736     }
1737 
1738 	//set an arbitrary width for numbers
1739 	int wstring;
1740 	m_TexFontNumbers.GetTextExtent( _T("1234"), &wstring, NULL );
1741 
1742 	if( m_Settings.Settings[settings].m_bNumFixSpac ) {						//fixed spacing
1743 
1744 		//Set spacing between numbers
1745                 int space = adjustSpacing( m_Settings.Settings[settings].m_iNumbersSpacing);
1746 
1747 		PlugIn_ViewPort uvp = *vp;
1748 		uvp.rotation = uvp.skew = 0;
1749 
1750 		wxPoint ptl, pbr;
1751 		GetCanvasPixLL( &uvp, &ptl, wxMin(pGRA->getLatMax(), 89.0), pGRA->getLonMin() );	 //top left corner position
1752 		GetCanvasPixLL( &uvp, &pbr, wxMax(pGRA->getLatMin(), -89.0), pGRA->getLonMax() ); //bottom right corner position
1753 		if (ptl.x >= pbr.x) {
1754 		    // 360
1755 		    ptl.x = 0;
1756 		    pbr.x = m_ParentSize.GetWidth();
1757 		}
1758 
1759 		for( int i = wxMax(ptl.x, 0); i < wxMin(pbr.x, m_ParentSize.GetWidth() ) ; i+= (space + wstring) ) {
1760 			for( int j = wxMax(ptl.y, 0); j < wxMin(pbr.y, m_ParentSize.GetHeight() ); j+= (space + wstring) ) {
1761 				double lat, lon, val;
1762 				GetCanvasLLPix( vp, wxPoint( i, j ), &lat, &lon );
1763 				val = pGRA->getInterpolatedValue( lon, lat, true );
1764 				if( val != GRIB_NOTDEF ) {
1765 					double value = m_Settings.CalibrateValue(settings, val);
1766 					wxColour back_color = GetGraphicColor(settings, value);
1767 
1768 					DrawNumbers( wxPoint(i, j), value, settings, back_color );
1769 				}
1770 			}
1771 		}
1772 	} else {
1773 
1774 		//set minimum spacing between arrows
1775 		double minspace = wxMax( m_Settings.Settings[settings].m_iNumbersSpacing, wstring * 1.2 );
1776                 double minspace2 = square(minspace);
1777 
1778 		//    Get the the grid
1779 		int imax = pGRA->getNi();                  // Longitude
1780 		int jmax = pGRA->getNj();                  // Latitude
1781 
1782 		wxPoint firstpx(-1000, -1000);
1783 		wxPoint oldpx(-1000, -1000);
1784 		wxPoint oldpy(-1000, -1000);
1785 
1786 		for( int i = 0; i < imax; i++ ) {
1787 			double lonl, latl;
1788 			pGRA->getXY( i, pGRA->getNj()/2, &lonl, &latl );
1789 
1790 			wxPoint pl;
1791 			GetCanvasPixLL( vp, &pl, latl, lonl );
1792 
1793 			if (pl.x <= firstpx.x && square( pl.x - firstpx.x) + square(pl.y - firstpx.y ) < minspace2/1.44)
1794 			    continue;
1795 
1796 			if( square( pl.x - oldpx.x) + square(pl.y - oldpx.y ) >= minspace2 ) {
1797 				oldpx = pl;
1798 				if (i == 0)
1799 				    firstpx = pl;
1800 
1801 				for( int j = 0; j < jmax; j++ ) {
1802 					double lon, lat;
1803 					pGRA->getXY( i, j, &lon, &lat );
1804 
1805 					wxPoint p;
1806 					GetCanvasPixLL( vp, &p, lat, lon );
1807 
1808 					if( square( p.x - oldpy.x) + square(p.y - oldpy.y ) >= minspace2 ) {
1809 						oldpy = p;
1810 
1811 						if(lon > 180)
1812 							lon -= 360;
1813 
1814 						if( PointInLLBox( vp, lon, lat ) ) {
1815 							double mag = pGRA->getValue( i, j );
1816 
1817 							if( mag != GRIB_NOTDEF ) {
1818 								double value = m_Settings.CalibrateValue(settings, mag);
1819 								wxColour back_color = GetGraphicColor(settings, value);
1820 
1821 								DrawNumbers( p, value, settings, back_color );
1822 							}
1823 						}
1824                     }
1825                 }
1826             }
1827         }
1828     }
1829 
1830     delete pGRM;
1831 }
1832 
DrawNumbers(wxPoint p,double value,int settings,wxColour back_color)1833 void GRIBOverlayFactory::DrawNumbers( wxPoint p, double value, int settings, wxColour back_color )
1834 {
1835 	if( m_pdc ) {
1836 		wxImage &label = getLabel(value, settings, back_color);
1837         //set alpha chanel
1838         int w = label.GetWidth(), h = label.GetHeight();
1839         for( int y = 0; y < h; y++ )
1840 			for( int x = 0; x < w; x++ )
1841 				label.SetAlpha( x, y, m_Settings.m_iOverlayTransparency );
1842 
1843                 m_pdc->DrawBitmap(label, p.x, p.y, true);
1844     } else {
1845 #ifdef ocpnUSE_GL
1846 #ifndef USE_ANDROID_GLES2
1847 
1848 		glEnable( GL_BLEND );
1849         glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
1850         glColor4ub(back_color.Red(), back_color.Green(),
1851 					back_color.Blue(), m_Settings.m_iOverlayTransparency);
1852 
1853         glLineWidth(1);
1854 
1855         wxString label = getLabelString(value, settings);
1856         int w, h;
1857         m_TexFontNumbers.GetTextExtent( label, &w, &h );
1858 
1859         int label_offsetx = 5, label_offsety = 1;
1860         int x = p.x - label_offsetx, y = p.y - label_offsety;
1861         w += 2*label_offsetx, h += 2*label_offsety;
1862 
1863         /* draw bounding rectangle */
1864         glBegin(GL_QUADS);
1865         glVertex2i(x,   y);
1866         glVertex2i(x+w, y);
1867         glVertex2i(x+w, y+h);
1868         glVertex2i(x,   y+h);
1869         glEnd();
1870 
1871         glColor4ub( 0, 0, 0, m_Settings.m_iOverlayTransparency );
1872 
1873         glBegin(GL_LINE_LOOP);
1874         glVertex2i(x,   y);
1875         glVertex2i(x+w, y);
1876         glVertex2i(x+w, y+h);
1877         glVertex2i(x,   y+h);
1878         glEnd();
1879 
1880         glEnable(GL_TEXTURE_2D);
1881         m_TexFontNumbers.RenderString( label, p.x, p.y );
1882         glDisable(GL_TEXTURE_2D);
1883 #else
1884 
1885         #ifdef __WXQT__
1886         wxFont font = GetOCPNGUIScaledFont_PlugIn(_T("Dialog"));
1887         #else
1888         wxFont font( 9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
1889         #endif
1890 
1891         wxString label = getLabelString(value, settings);
1892 
1893         m_oDC->SetFont(font);
1894         int w, h;
1895         m_oDC->GetTextExtent(label, &w, &h);
1896 
1897         int label_offsetx = 5, label_offsety = 1;
1898         int x = p.x - label_offsetx, y = p.y - label_offsety;
1899         w += 2*label_offsetx, h += 2*label_offsety;
1900 
1901 
1902         m_oDC->SetBrush( wxBrush( back_color ) );
1903         m_oDC->DrawRoundedRectangle( x, y, w, h, 0);
1904 
1905             /* draw bounding rectangle */
1906         m_oDC->SetPen( wxPen( wxColour(0,0,0), 1 ) );
1907         m_oDC->DrawLine(x,   y,   x+w, y);
1908         m_oDC->DrawLine(x+w, y,   x+w, y+h);
1909         m_oDC->DrawLine(x+w, y+h, x,   y+h);
1910         m_oDC->DrawLine(x  , y+h, x,   y);
1911 
1912         m_oDC->DrawText(label, p.x, p.y);
1913 
1914 #endif
1915 #endif
1916 	}
1917 }
1918 
RenderGribParticles(int settings,GribRecord ** pGR,PlugIn_ViewPort * vp)1919 void GRIBOverlayFactory::RenderGribParticles( int settings, GribRecord **pGR,
1920                                               PlugIn_ViewPort *vp )
1921 {
1922     if(!m_Settings.Settings[settings].m_bParticles)
1923         return;
1924 
1925     //   need two records or a polar record to draw arrows
1926     GribRecord *pGRX, *pGRY;
1927     int idx, idy;
1928     bool polar;
1929     SettingsIdToGribId(settings, idx, idy, polar);
1930     if(idx < 0 || idy < 0)
1931         return;
1932 
1933     pGRX = pGR[idx];
1934     pGRY = pGR[idy];
1935 
1936     if(!pGRX || !pGRY)
1937         return;
1938 
1939     wxStopWatch sw;
1940     sw.Start();
1941 
1942     if(m_ParticleMap && m_ParticleMap->m_Setting != settings)
1943         ClearParticles();
1944 
1945     if(!m_ParticleMap)
1946         m_ParticleMap = new ParticleMap(settings);
1947 
1948     std::vector<Particle> &particles = m_ParticleMap->m_Particles;
1949 
1950     const int max_duration = 50;
1951     const int run_count = 6;
1952 
1953     double density = m_Settings.Settings[settings].m_dParticleDensity;
1954 //    density = density * sqrt(vp.view_scale_ppm);
1955 
1956     int history_size = 27 / sqrt(density);
1957     history_size = wxMin(history_size, MAX_PARTICLE_HISTORY);
1958 
1959     std::vector<Particle>::iterator it;
1960     // if the history size changed
1961     if(m_ParticleMap->history_size != history_size) {
1962         for(unsigned int i = 0; i < particles.size(); i++) {
1963             Particle &it = particles[i];
1964             if(m_ParticleMap->history_size > history_size &&
1965                it.m_HistoryPos >= history_size) {
1966                 it = particles[particles.size() - 1];
1967                 particles.pop_back();
1968                 i--;
1969                 continue;
1970             }
1971 
1972             it.m_HistorySize = it.m_HistoryPos+1;
1973         }
1974         m_ParticleMap->history_size = history_size;
1975     }
1976 
1977     // Did the viewport change?  update cached screen coordinates
1978     // we could use normalized coordinates in opengl and avoid this
1979     PlugIn_ViewPort &lvp = m_ParticleMap->last_viewport;
1980     if(lvp.bValid == false || vp->view_scale_ppm != lvp.view_scale_ppm
1981         || vp->skew != lvp.skew || vp->rotation != lvp.rotation) {
1982         for(it = particles.begin(); it != particles.end(); it++)
1983             for(int i=0; i<it->m_HistorySize; i++) {
1984                 Particle::ParticleNode &n = it->m_History[i];
1985                 float (&p)[2] = n.m_Pos;
1986                 if(p[0] == -10000)
1987                     continue;
1988 
1989                 wxPoint ps;
1990                 GetCanvasPixLL( vp, &ps, p[1], p[0] );
1991                 n.m_Screen[0] = ps.x;
1992                 n.m_Screen[1] = ps.y;
1993             }
1994 
1995         lvp = *vp;
1996     } else // just panning, do quicker update
1997         if(vp->clat != lvp.clat || vp->clon != lvp.clon) {
1998             wxPoint p1, p2;
1999             GetCanvasPixLL( vp, &p1, 0, 0 );
2000             GetCanvasPixLL( &lvp, &p2, 0, 0 );
2001 
2002             p1 -= p2;
2003 
2004             for(it = particles.begin(); it != particles.end(); it++)
2005                 for(int i=0; i<it->m_HistorySize; i++) {
2006                     Particle::ParticleNode &n = it->m_History[i];
2007                     float (&p)[2] = n.m_Pos;
2008                     if(p[0] == -10000)
2009                         continue;
2010 
2011                     n.m_Screen[0] += p1.x;
2012                     n.m_Screen[1] += p1.y;
2013                 }
2014             lvp = *vp;
2015         }
2016 
2017     double ptime = 0;
2018 
2019     // update particle map
2020     if(m_bUpdateParticles) {
2021         for(unsigned int i = 0; i < particles.size(); i++) {
2022             Particle &it = particles[i];
2023 
2024             // Update the interpolation factor
2025             if(++it.m_Run < run_count)
2026                 continue;
2027             it.m_Run = 0;
2028 
2029             // don't allow particle to live too long
2030             if(it.m_Duration > max_duration) {
2031                 it = particles[particles.size() - 1];
2032                 particles.pop_back();
2033                 i--;
2034                 continue;
2035             }
2036 
2037             it.m_Duration++;
2038 
2039             float (&pp)[2] = it.m_History[it.m_HistoryPos].m_Pos;
2040 
2041             // maximum history size
2042             if(++it.m_HistorySize > history_size)
2043                 it.m_HistorySize = history_size;
2044 
2045             if(++it.m_HistoryPos >= history_size)
2046                 it.m_HistoryPos = 0;
2047 
2048             Particle::ParticleNode &n = it.m_History[it.m_HistoryPos];
2049             float (&p)[2] = n.m_Pos;
2050             double vkn=0, ang;
2051 
2052             if(it.m_Duration < max_duration - history_size &&
2053                GribRecord::getInterpolatedValues(vkn, ang, pGRX, pGRY, pp[0], pp[1]) &&
2054                vkn > 0 && vkn < 100 ) {
2055 
2056                 vkn = m_Settings.CalibrateValue(settings, vkn);
2057                 double d;
2058                 if(settings == GribOverlaySettings::CURRENT)
2059                     d = vkn*run_count;
2060                 else
2061                     d = vkn*run_count/4;
2062 
2063                 ang += 180;
2064 
2065 #if 0 // elliptical very accurate but incredibly slow
2066                 double dp[2];
2067                 PositionBearingDistanceMercator_Plugin(pp[1], pp[0], ang,
2068                                                        d, &dp[1], &dp[0]);
2069                 p[0] = dp[0];
2070                 p[1] = dp[1];
2071 #elif 0 // really fast rectangular.. not really good at high latitudes
2072 
2073                 float angr = ang/180*M_PI;
2074                 p[0] = pp[0] + sinf(angr)*d/60;
2075                 p[1] = pp[1] + cosf(angr)*d/60;
2076 #else // spherical (close enough)
2077                 float angr = ang/180*M_PI;
2078                 float latr = pp[1]*M_PI/180;
2079                 float D = d/3443; // earth radius in nm
2080                 float sD = sinf(D), cD = cosf(D);
2081                 float sy = sinf(latr), cy = cosf(latr);
2082                 float sa = sinf(angr), ca = cosf(angr);
2083 
2084                 p[0] = pp[0] + asinf(sa*sD/cy) * 180/M_PI;
2085                 p[1] = asinf(sy*cD + cy*sD*ca) * 180/M_PI;
2086 #endif
2087                 wxPoint ps;
2088                 GetCanvasPixLL( vp, &ps, p[1], p[0] );
2089 
2090                 n.m_Screen[0] = ps.x;
2091                 n.m_Screen[1] = ps.y;
2092 
2093                 wxColor c = GetGraphicColor(settings, vkn);
2094 
2095                 n.m_Color[0] = c.Red();
2096                 n.m_Color[1] = c.Green();
2097                 n.m_Color[2] = c.Blue();
2098             } else
2099                 p[0] = -10000;
2100             ptime += sw.Time();
2101         }
2102     }
2103     m_bUpdateParticles = false;
2104 
2105     int total_particles = density * pGRX->getNi() * pGRX->getNj();
2106 
2107     // set max cap to avoid locking the program up
2108     if(total_particles > 60000)
2109         total_particles = 60000;
2110 
2111     // remove particles if needed;
2112     int remove_particles = ((int)particles.size() - total_particles) / 16;
2113     for(int i = 0; i<remove_particles; i++)
2114         particles.pop_back();
2115 
2116     // add new particles as needed
2117     int run = 0;
2118     int new_particles = (total_particles - (int)particles.size()) / 64;
2119 
2120     for(int npi=0; npi<new_particles; npi++) {
2121         float p[2];
2122         double vkn, ang;
2123         for(int i=0; i<20; i++) {
2124             // random position in the grib area
2125             p[0] = (float)rand() / RAND_MAX * (pGRX->getLonMax() - pGRX->getLonMin()) + pGRX->getLonMin();
2126             p[1] = (float)rand() / RAND_MAX * (pGRX->getLatMax() - pGRX->getLatMin()) + pGRX->getLatMin();
2127 
2128             if(GribRecord::getInterpolatedValues(vkn, ang, pGRX, pGRY, p[0], p[1]) &&
2129                vkn > 0 && vkn < 100)
2130                 vkn = m_Settings.CalibrateValue(settings, vkn);
2131             else
2132                 continue; // try again
2133 
2134             /* try hard to find a random position where current is faster than 1 knot */
2135             if(settings != GribOverlaySettings::CURRENT || vkn > 1 - (double)i/20)
2136                 break;
2137         }
2138 
2139         Particle np;
2140         np.m_Duration = rand()%(max_duration/2);
2141         np.m_HistoryPos = 0;
2142         np.m_HistorySize = 1;
2143         np.m_Run = run++;
2144         if(run == run_count)
2145             run = 0;
2146 
2147         memcpy(np.m_History[np.m_HistoryPos].m_Pos, p, sizeof p);
2148 
2149         wxPoint ps;
2150         GetCanvasPixLL( vp, &ps, p[1], p[0]);
2151         np.m_History[np.m_HistoryPos].m_Screen[0] = ps.x;
2152         np.m_History[np.m_HistoryPos].m_Screen[1] = ps.y;
2153 
2154         wxColour c = GetGraphicColor(settings, vkn);
2155         np.m_History[np.m_HistoryPos].m_Color[0] = c.Red();
2156         np.m_History[np.m_HistoryPos].m_Color[1] = c.Green();
2157         np.m_History[np.m_HistoryPos].m_Color[2] = c.Blue();
2158 
2159         particles.push_back(np);
2160     }
2161 
2162     // settings for opengl lines
2163     if( !m_pdc ) {
2164         //      Enable anti-aliased lines, at best quality
2165         glEnable( GL_LINE_SMOOTH );
2166         glEnable( GL_BLEND );
2167         glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
2168         glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
2169         glLineWidth( 2.3f );
2170     }
2171 
2172     int cnt=0;
2173     unsigned char *&ca = m_ParticleMap->color_array;
2174     float *&va = m_ParticleMap->vertex_array;
2175     float *&caf = m_ParticleMap->color_float_array;
2176 
2177     if(m_ParticleMap->array_size < particles.size() && !m_pdc) {
2178         m_ParticleMap->array_size = 2*particles.size();
2179         delete [] ca;
2180         delete [] va;
2181         delete [] caf;
2182 
2183         ca = new unsigned char[m_ParticleMap->array_size * MAX_PARTICLE_HISTORY * 8];
2184         caf = new float[m_ParticleMap->array_size * MAX_PARTICLE_HISTORY * 8];
2185         va = new float[m_ParticleMap->array_size * MAX_PARTICLE_HISTORY * 4];
2186     }
2187 
2188     // draw particles
2189     for(std::vector<Particle>::iterator it = particles.begin();
2190         it != particles.end(); it++) {
2191 
2192         wxUint8 alpha = 250;
2193 
2194         int i = it->m_HistoryPos;
2195 
2196         bool lip_valid = false;
2197         float *lp = NULL, lip[2];
2198         wxUint8 lc[4];
2199         float lcf[4];
2200 
2201         for(;;) {
2202             float (&dp)[2] = it->m_History[i].m_Pos;
2203             if(dp[0] != -10000) {
2204                 float (&sp)[2] = it->m_History[i].m_Screen;
2205                 wxUint8 (&ci)[3] = it->m_History[i].m_Color;
2206 
2207                 wxUint8 c[4] = {ci[0], ci[1], (unsigned char)(ci[2] + 240-alpha/2), alpha};
2208                 float cf[4];
2209                 cf[0] = ci[0] / 256.;
2210                 cf[1] = ci[1] / 256.;
2211                 cf[2] = ((unsigned char)(ci[2] + 240-alpha/2)) / 256.;
2212                 cf[3] = alpha / 256.;
2213 
2214                 if(lp && fabsf(lp[0]-sp[0]) < vp->pix_width) {
2215                     float sip[2];
2216 
2217                     // interpolate between points..  a cubic interpolation
2218                     // might allow a much higher run_count
2219                     float d = (float)it->m_Run/run_count;
2220                     for(int j=0; j<2; j++)
2221                         sip[j] = d*lp[j] + (1-d)*sp[j];
2222 
2223                     if(lip_valid && fabsf(lip[0] - sip[0]) < vp->pix_width) {
2224                         if( m_pdc ) {
2225                             m_pdc->SetPen(wxPen( wxColour(c[0], c[1], c[2]), 2 ));
2226                             m_pdc->DrawLine( sip[0], sip[1], lip[0], lip[1] );
2227                         } else {
2228                             memcpy(ca + 4*cnt, c, sizeof lc);
2229                             memcpy(caf + 4*cnt, cf, sizeof lcf);
2230                             memcpy(va + 2*cnt, lip, sizeof sp);
2231                             cnt++;
2232                             memcpy(ca + 4*cnt, lc, sizeof c);
2233                             memcpy(caf + 4*cnt, lcf, sizeof cf);
2234                             memcpy(va + 2*cnt, sip, sizeof sp);
2235                             cnt++;
2236                         }
2237                     }
2238 
2239                     memcpy(lip, sip, sizeof lip);
2240                     lip_valid = true;
2241                 }
2242 
2243                 memcpy(lc, c, sizeof lc);
2244                 memcpy(lcf, cf, sizeof lcf);
2245 
2246                 lp = sp;
2247             }
2248 
2249             if(--i < 0) {
2250                 i = history_size - 1;
2251                 if(i >= it->m_HistorySize)
2252                     break;
2253             }
2254 
2255             if(i == it->m_HistoryPos)
2256                 break;
2257 
2258             alpha -= 240 / history_size;
2259         }
2260     }
2261 
2262     if( !m_pdc ) {
2263         if(m_oDC){
2264             m_oDC->DrawGLLineArray(cnt, va, caf, ca, false);
2265         }
2266     }
2267 
2268     //  On some platforms, especially slow ones, the GPU will lag behind the CPU.
2269     //  This affects the UI in strange ways.
2270     //  So, force the GPU to flush all of its outstanding commands on the outer loop
2271     //  This will have no real affect on most machines.
2272 #ifdef __WXMSW__
2273     if( !m_pdc )
2274         glFlush();
2275 #endif
2276 
2277     int time = sw.Time();
2278 
2279     //  Try to run at 20 fps,
2280     //  But also arrange not to consume more than 33% CPU(core) duty cycle
2281     m_tParticleTimer.Start(wxMax(50 - time, 2 * time), wxTIMER_ONE_SHOT);
2282 
2283 #if 0
2284     static int total_time;
2285     total_time += time;
2286     static int total_count;
2287     if(++total_count == 100) {
2288         printf("time: %.2f\n", (double)total_time / total_count);
2289         total_time = total_count = 0;
2290     }
2291 #endif
2292 }
2293 
OnParticleTimer(wxTimerEvent & event)2294 void GRIBOverlayFactory::OnParticleTimer( wxTimerEvent & event )
2295 {
2296     m_bUpdateParticles = true;
2297 
2298     // If multicanvas are active, render the overlay on the right canvas only
2299     if(GetCanvasCount() > 1)            // multi?
2300         GetCanvasByIndex(1)->Refresh(false);     // update the last rendered canvas
2301     else
2302         GetOCPNCanvasWindow()->Refresh(false);
2303 }
2304 
DrawMessageWindow(wxString msg,int x,int y,wxFont * mfont)2305 void GRIBOverlayFactory::DrawMessageWindow( wxString msg, int x, int y , wxFont *mfont)
2306 {
2307     if(msg.empty())
2308         return;
2309 
2310     if(m_pdc) {
2311         wxDC &dc = *m_pdc;
2312         dc.SetFont( *mfont );
2313         dc.SetPen( *wxTRANSPARENT_PEN);
2314 
2315         dc.SetBrush( wxColour(243, 229, 47 ) );
2316         int w, h;
2317         dc.GetMultiLineTextExtent( msg, &w, &h );
2318         h += 2;
2319         int yp = y - ( 2 * GetChartbarHeight() + h );
2320 
2321         int label_offset = 10;
2322         int wdraw = w + ( label_offset * 2 );
2323         dc.DrawRectangle( 0, yp, wdraw, h );
2324         dc.DrawLabel( msg, wxRect( label_offset, yp, wdraw, h ),
2325                       wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
2326     } else {
2327 
2328         if(m_oDC){
2329             m_oDC->SetFont( *mfont );
2330             m_oDC->SetPen( *wxTRANSPARENT_PEN);
2331 
2332             m_oDC->SetBrush( wxColour(243, 229, 47 ) );
2333             int w, h;
2334             m_oDC->GetTextExtent( msg, &w, &h );
2335             h += 2;
2336             int yp = y - ( 2 * GetChartbarHeight() + h );
2337 
2338             int label_offset = 10;
2339             int wdraw = w + ( label_offset * 2 );
2340             m_oDC->DrawRectangle( 0, yp, wdraw, h );
2341             m_oDC->DrawText( msg, label_offset, yp );
2342 
2343         }
2344 /*
2345         m_TexFontMessage.Build(*mfont);
2346         int w, h;
2347         m_TexFontMessage.GetTextExtent( msg, &w, &h);
2348         h += 2;
2349         int yp = y - ( 2 * GetChartbarHeight() + h );
2350 
2351         glColor3ub( 243, 229, 47 );
2352 
2353         glBegin(GL_QUADS);
2354         glVertex2i(0, yp);
2355         glVertex2i(w, yp);
2356         glVertex2i(w, yp+h);
2357         glVertex2i(0, yp+h);
2358         glEnd();
2359 
2360         glEnable(GL_BLEND);
2361         glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
2362 
2363         glColor3ub( 0, 0, 0 );
2364         glEnable(GL_TEXTURE_2D);
2365         m_TexFontMessage.RenderString( msg, 0, yp);
2366         glDisable(GL_TEXTURE_2D);
2367 */
2368     }
2369 }
2370 
drawDoubleArrow(int x,int y,double ang,wxColour arrowColor,int arrowWidth,int arrowSizeIdx,double scale)2371 void GRIBOverlayFactory::drawDoubleArrow( int x, int y, double ang, wxColour arrowColor, int arrowWidth, int arrowSizeIdx, double scale )
2372 {
2373     if( m_pdc ) {
2374         wxPen pen( arrowColor, 2 );
2375         m_pdc->SetPen( pen );
2376         m_pdc->SetBrush( *wxTRANSPARENT_BRUSH);
2377 #if wxUSE_GRAPHICS_CONTEXT
2378 		if (m_hiDefGraphics && m_gdc)
2379 			m_gdc->SetPen(pen);
2380 #endif
2381     } else {
2382         if(m_oDC){
2383             wxPen pen( arrowColor, arrowWidth );
2384             m_oDC->SetPen( pen );
2385         }
2386     }
2387 
2388     drawLineBuffer(m_DoubleArrow[arrowSizeIdx], x, y, ang, scale);
2389 }
2390 
drawSingleArrow(int x,int y,double ang,wxColour arrowColor,int arrowWidth,int arrowSizeIdx,double scale)2391 void GRIBOverlayFactory::drawSingleArrow( int x, int y, double ang, wxColour arrowColor, int arrowWidth, int arrowSizeIdx, double scale )
2392 {
2393     if( m_pdc ) {
2394         wxPen pen( arrowColor, arrowWidth );
2395         m_pdc->SetPen( pen );
2396         m_pdc->SetBrush( *wxTRANSPARENT_BRUSH);
2397 #if wxUSE_GRAPHICS_CONTEXT
2398 		if (m_hiDefGraphics && m_gdc)
2399 			m_gdc->SetPen(pen);
2400 #endif
2401     } else {
2402         if(m_oDC){
2403             wxPen pen( arrowColor, arrowWidth );
2404             m_oDC->SetPen( pen );
2405         }
2406     }
2407 
2408     drawLineBuffer(m_SingleArrow[arrowSizeIdx], x, y, ang, scale);
2409 }
2410 
drawWindArrowWithBarbs(int settings,int x,int y,double vkn,double ang,bool south,wxColour arrowColor,double rotate_angle)2411 void GRIBOverlayFactory::drawWindArrowWithBarbs( int settings, int x, int y, double vkn, double ang, bool south,
2412                                                  wxColour arrowColor, double rotate_angle )
2413 {
2414     if(m_Settings.Settings[settings].m_iBarbedColour == 1)
2415         arrowColor = GetGraphicColor(settings, vkn);
2416 
2417     float penWidth = .4 / m_pixelMM;
2418 
2419     if( m_pdc ) {
2420         wxPen pen( arrowColor, 2 );
2421         m_pdc->SetPen( pen );
2422         m_pdc->SetBrush( *wxTRANSPARENT_BRUSH);
2423 
2424 #if wxUSE_GRAPHICS_CONTEXT
2425         if( m_hiDefGraphics && m_gdc )
2426             m_gdc->SetPen( pen );
2427 #endif
2428     }
2429 #ifdef ocpnUSE_GL
2430     else{
2431         if(m_oDC){
2432             wxPen pen( arrowColor, penWidth );
2433             m_oDC->SetPen( pen );
2434         }
2435 //         else
2436 //             glColor3ub(arrowColor.Red(), arrowColor.Green(), arrowColor.Blue());
2437     }
2438 #endif
2439 
2440     int cacheidx;
2441 
2442     if( vkn < 1 )
2443         cacheidx = 0;
2444     else if( vkn < 2.5)
2445         cacheidx = 1;
2446     else if( vkn < 40 )
2447         cacheidx = (int)(vkn + 2.5) / 5;
2448     else if( vkn < 90 )
2449         cacheidx = (int)(vkn + 5) / 10 + 4;
2450     else
2451         cacheidx = 13;
2452 
2453     ang += rotate_angle;
2454 
2455     drawLineBuffer(m_WindArrowCache[cacheidx], x, y, ang, 1.0, south, m_bDrawBarbedArrowHead);
2456 }
2457 
drawLineBuffer(LineBuffer & buffer,int x,int y,double ang,double scale,bool south,bool head)2458 void GRIBOverlayFactory::drawLineBuffer(LineBuffer &buffer, int x, int y, double ang, double scale,bool south, bool head)
2459 {
2460     // transform vertexes by angle
2461     float six = sinf( ang ), cox = cosf( ang ), siy, coy;
2462     if(south)
2463         siy = -six, coy = -cox;
2464     else
2465         siy = six, coy = cox;
2466 
2467     float vertexes[40];
2468     int count = buffer.count;
2469 
2470     if (!head) {
2471         count -= 2;
2472     }
2473     wxASSERT(sizeof vertexes / sizeof *vertexes >= (unsigned)count*4);
2474     for(int i=0; i < 2*count; i++) {
2475         int j = i;
2476         if (!head && i > 1)
2477             j += 4;
2478         float *k = buffer.lines + 2*j;
2479         vertexes[2*i+0] = k[0]*cox*scale + k[1]*siy*scale + x;
2480         vertexes[2*i+1] = k[0]*six*scale - k[1]*coy*scale + y;
2481     }
2482 
2483     if( m_pdc ) {
2484         for(int i=0; i < count; i++) {
2485             float *l = vertexes + 4*i;
2486 #if wxUSE_GRAPHICS_CONTEXT
2487             if( m_hiDefGraphics && m_gdc )
2488                 m_gdc->StrokeLine( l[0], l[1], l[2], l[3] );
2489             else
2490 #endif
2491                 m_pdc->DrawLine( l[0], l[1], l[2], l[3] );
2492         }
2493     } else {                       // OpenGL mode
2494 #ifdef ocpnUSE_GL
2495     if(m_oDC){
2496         for(int i=0; i < count; i++) {
2497             float *l = vertexes + 4*i;
2498             if( m_hiDefGraphics )
2499                 m_oDC->StrokeLine( l[0], l[1], l[2], l[3] );
2500             else
2501                 m_oDC->DrawLine( l[0], l[1], l[2], l[3] );
2502         }
2503     }
2504 
2505 //        glVertexPointer(2, GL_FLOAT, 2*sizeof(float), vertexes);
2506 //        glDrawArrays(GL_LINES, 0, 2*count);
2507 #endif
2508     }
2509 }
2510 
2511 #ifdef ocpnUSE_GL
2512 //      Render a texture
2513 //      x/y : origin in screen pixels of UPPER RIGHT corner of render rectangle
2514 //      width/height : in screen pixels
DrawSingleGLTexture(GribOverlay * pGO,GribRecord * pGR,double uv[],double x,double y,double width,double height)2515 void GRIBOverlayFactory::DrawSingleGLTexture( GribOverlay *pGO, GribRecord *pGR, double uv[], double x, double y, double width, double height ){
2516 
2517 #ifdef __OCPN__ANDROID__
2518 
2519     glEnable(texture_format);
2520 
2521     glEnable(GL_BLEND);
2522     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2523 
2524     glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
2525 
2526     float coords[8];
2527 
2528     coords[0] = -width; coords[1] = -height;
2529     coords[2] = 0; coords[3] = -height;
2530     coords[4] = 0; coords[5] = 0;
2531     coords[6] = -width; coords[7] = 0;
2532 
2533     extern int pi_texture_2D_shader_program;
2534     glUseProgram( pi_texture_2D_shader_program );
2535 
2536     // Get pointers to the attributes in the program.
2537     GLint mPosAttrib = glGetAttribLocation( pi_texture_2D_shader_program, "aPos" );
2538     GLint mUvAttrib  = glGetAttribLocation( pi_texture_2D_shader_program, "aUV" );
2539 
2540     // Set up the texture sampler to texture unit 0
2541     GLint texUni = glGetUniformLocation( pi_texture_2D_shader_program, "uTex" );
2542     glUniform1i( texUni, 0 );
2543 
2544     // Disable VBO's (vertex buffer objects) for attributes.
2545     glBindBuffer( GL_ARRAY_BUFFER, 0 );
2546     glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
2547 
2548     // Set the attribute mPosAttrib with the vertices in the screen coordinates...
2549     glVertexAttribPointer( mPosAttrib, 2, GL_FLOAT, GL_FALSE, 0, coords );
2550     // ... and enable it.
2551     glEnableVertexAttribArray( mPosAttrib );
2552 
2553     // Set the attribute mUvAttrib with the vertices in the GL coordinates...
2554     glVertexAttribPointer( mUvAttrib, 2, GL_FLOAT, GL_FALSE, 0, uv );
2555     // ... and enable it.
2556     glEnableVertexAttribArray( mUvAttrib );
2557 
2558     // Rotate
2559     float angle = 0;
2560     mat4x4 I, Q;
2561     mat4x4_identity(I);
2562     mat4x4_rotate_Z(Q, I, angle);
2563 
2564     // Translate
2565      Q[3][0] = x;
2566      Q[3][1] = y;
2567 
2568 
2569     GLint matloc = glGetUniformLocation(pi_texture_2D_shader_program,"TransformMatrix");
2570     glUniformMatrix4fv( matloc, 1, GL_FALSE, (const GLfloat*)Q);
2571 
2572     // Select the active texture unit.
2573     glActiveTexture( GL_TEXTURE0 );
2574 
2575     // Perform the actual drawing.
2576 
2577     // For some reason, glDrawElements is busted on Android
2578     // So we do this a hard ugly way, drawing two triangles...
2579     #if 0
2580     GLushort indices1[] = {0,1,3,2};
2581     glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices1);
2582     #else
2583 
2584     float co1[8];
2585     co1[0] = coords[0];
2586     co1[1] = coords[1];
2587     co1[2] = coords[2];
2588     co1[3] = coords[3];
2589     co1[4] = coords[6];
2590     co1[5] = coords[7];
2591     co1[6] = coords[4];
2592     co1[7] = coords[5];
2593 
2594     float tco1[8];
2595     tco1[0] = uv[0];
2596     tco1[1] = uv[1];
2597     tco1[2] = uv[2];
2598     tco1[3] = uv[3];
2599     tco1[4] = uv[6];
2600     tco1[5] = uv[7];
2601     tco1[6] = uv[4];
2602     tco1[7] = uv[5];
2603 
2604     glVertexAttribPointer( mPosAttrib, 2, GL_FLOAT, GL_FALSE, 0, co1 );
2605     glVertexAttribPointer( mUvAttrib, 2, GL_FLOAT, GL_FALSE, 0, tco1 );
2606 
2607     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
2608 
2609     glDisable(GL_BLEND);
2610     glDisable(texture_format);
2611 
2612     // Restore identity matrix
2613     mat4x4_identity(I);
2614     glUniformMatrix4fv( matloc, 1, GL_FALSE, (const GLfloat*)I);
2615 
2616     #endif
2617 
2618 #else
2619 
2620     glColor4f(1, 1, 1, 1);
2621     glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
2622 
2623     if(texture_format != GL_TEXTURE_2D) {
2624         for(int i = 0 ; i < 4 ; i++){
2625             uv[i*2] *= pGR->getNi();
2626             uv[(i*2)+1] *= pGR->getNj();
2627         }
2628     }
2629 
2630     glBegin(GL_QUADS);
2631         glTexCoord2d(uv[0], uv[1]), glVertex2f(x-width, y-height);
2632         glTexCoord2d(uv[2], uv[3]), glVertex2f(x   , y-height);
2633         glTexCoord2d(uv[4], uv[5]), glVertex2f(x   , y);
2634         glTexCoord2d(uv[6], uv[7]), glVertex2f(x-width, y);
2635     glEnd();
2636 
2637 #endif
2638 }
2639 
DrawGLTexture(GribOverlay * pGO,GribRecord * pGR,PlugIn_ViewPort * vp)2640 void GRIBOverlayFactory::DrawGLTexture( GribOverlay *pGO, GribRecord *pGR, PlugIn_ViewPort *vp )
2641 {
2642     glEnable(texture_format);
2643     glBindTexture(texture_format, pGO->m_iTexture);
2644 
2645     glEnable(GL_BLEND);
2646     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2647 
2648     double lat_min = pGR->getLatMin(), lon_min = pGR->getLonMin();
2649 
2650     bool repeat = pGR->getLonMin() == 0 && pGR->getLonMax() + pGR->getDi() == 360;
2651 
2652     // how to break screen up, because projections may not be linear
2653     // smaller values offer more precision but become irrelevant
2654     // at lower zoom levels and near poles, use smaller tiles
2655 
2656     // This formula is generally "good enough" but is not optimal,
2657     // certainly not for all projections, and may result in
2658     // more tiles than actually needed in some cases
2659 
2660     double pw = vp->view_scale_ppm * 1e6/(pow(2, fabs(vp->clat)/25));
2661     if(pw < 20) // minimum 20 pixel to avoid too many tiles
2662         pw = 20;
2663 
2664     int xsquares = ceil(vp->pix_width/pw), ysquares = ceil(vp->pix_height/pw);
2665 
2666     // optimization for non-rotated mercator, since longitude is linear
2667     if(vp->rotation == 0 && vp->m_projection_type == PI_PROJECTION_MERCATOR)
2668         xsquares = 1;
2669 
2670     // It is possible to have only 1 square when the viewport covers more than
2671     // 180 longitudes but there is more logic needed.  This is simpler.
2672 //    if(vp->lon_max - vp->lon_min >= 180) {
2673         xsquares = wxMax(xsquares, 2);
2674         ysquares = wxMax(ysquares, 2);
2675 //    }
2676 
2677     double xs = vp->pix_width/double(xsquares), ys = vp->pix_height/double(ysquares);
2678     int i = 0, j = 0;
2679     typedef double mx[2][2];
2680 
2681     mx *lva = new mx[xsquares+1];
2682     int tw = pGO->m_iTextureDim[0], th = pGO->m_iTextureDim[1];
2683     double latstep = fabs(pGR->getDj()) / (th-2-1) * (pGR->getNj()-1);
2684     double lonstep = pGR->getDi() / (tw-2*!repeat-1) * (pGR->getNi()-1);
2685 
2686     double potNormX = (double)pGO->m_iTexDataDim[0] / tw;
2687     double potNormY = (double)pGO->m_iTexDataDim[1] / th;
2688 
2689     double clon = (lon_min + pGR->getLonMax())/2;
2690 
2691     for(double y = 0; y < vp->pix_height+ys/2; y += ys) {
2692         i = 0;
2693         for(double x = 0; x < vp->pix_width+xs/2; x += xs) {
2694             double lat, lon;
2695             wxPoint p(x, y);
2696             GetCanvasLLPix(vp, p, &lat, &lon);
2697 
2698             if(!repeat) {
2699                 if(clon - lon > 180)
2700                     lon += 360;
2701                 else if(lon - clon > 180)
2702                     lon -= 360;
2703             }
2704 
2705             lva[i][j][0] = (((lon - lon_min) / lonstep - repeat + 1.5) / tw) * potNormX;
2706             lva[i][j][1] = (((lat - lat_min) / latstep          + 1.5) / th) * potNormY;
2707 
2708             if(pGR->getDj() < 0)
2709                 lva[i][j][1] = 1 - lva[i][j][1];
2710 
2711             if(x > 0 && y > 0) {
2712                 double u0 = lva[i-1][!j][0], v0 = lva[i-1][!j][1];
2713                 double u1 = lva[i  ][!j][0], v1 = lva[i  ][!j][1];
2714                 double u2 = lva[i  ][ j][0], v2 = lva[i  ][ j][1];
2715                 double u3 = lva[i-1][ j][0], v3 = lva[i-1][ j][1];
2716 
2717                 if(repeat) { /* ensure all 4 texcoords are in the same phase */
2718                     if(u1 - u0 > .5) u1--; else if(u0 - u1 > .5) u1++;
2719                     if(u2 - u0 > .5) u2--; else if(u0 - u2 > .5) u2++;
2720                     if(u3 - u0 > .5) u3--; else if(u0 - u3 > .5) u3++;
2721                 }
2722 
2723                 if((repeat ||
2724                     ((u0 >= 0 || u1 >= 0 || u2 >= 0 || u3 >= 0) && // optimzations
2725                      (u0 <= 1 || u1 <= 1 || u2 <= 1 || u3 <= 1))) &&
2726                    (v0 >= 0 || v1 >= 0 || v2 >= 0 || v3 >= 0) &&
2727                    (v0 <= 1 || v1 <= 1 || v2 <= 1 || v3 <= 1)) {
2728 
2729                        double uv[8];
2730                        uv[0] = u0; uv[1] = v0;
2731                        uv[2] = u1; uv[3] = v1;
2732                        uv[4] = u2; uv[5] = v2;
2733                        uv[6] = u3; uv[7] = v3;
2734 
2735                        DrawSingleGLTexture( pGO, pGR, uv, x, y, xs, ys );
2736 
2737                 }
2738             }
2739 
2740             i++;
2741         }
2742         j = !j;
2743     }
2744     delete [] lva;
2745 
2746     glDisable(GL_BLEND);
2747     glDisable(texture_format);
2748 
2749 }
2750 #endif
2751