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