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