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 "NOAA_GFS";
97 case NOAA_NCEP_WW3: return "NOAA_NCEP_WW3";
98 case NOAA_NCEP_SST: return "NOAA_NCEP_SST";
99 case NOAA_RTOFS: return "NOAA_RTOFS";
100 case FNMOC_WW3_GLB: return "FNMOC_WW3";
101 case FNMOC_WW3_MED: return "FNMOC_WW3";
102 case NORWAY_METNO: return "NORWAY_METNO";
103 default : return "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"));
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("\n");
598 m_Message_Hiden.Append(_("Warning : Data at Geopotential Height"))
599 .Append(" ")
600 .Append(m_Settings.GetAltitudeFromIndex(
601 m_Altitude,
602 m_Settings.Settings[GribOverlaySettings::PRESSURE].m_Units))
603 .Append(" ")
604 .Append(m_Settings.GetUnitSymbol(GribOverlaySettings::PRESSURE))
605 .Append(" ! ");
606 }
607 if (m_dlg.ProjectionEnabled()) {
608 int x, y;
609 m_dlg.GetProjectedLatLon(x, y, vp);
610 DrawProjectedPosition(x, y);
611 }
612 if (!m_Message_Hiden.IsEmpty()) m_Message_Hiden.Append("\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, "#d90000"}, {1, "#d92a00"}, {2, "#d96e00"}, {3, "#d9b200"},
959 {4, "#d4d404"}, {5, "#a6d906"}, {7, "#06d9a0"}, {9, "#00d9b0"},
960 {12, "#00d9c0"}, {15, "#00aed0"}, {18, "#0083e0"}, {21, "#0057e0"},
961 {24, "#0000f0"}, {27, "#0400f0"}, {30, "#1c00f0"}, {36, "#4800f0"},
962 {42, "#6900f0"}, {48, "#a000f0"}, {56, "#f000f0"}};
963
964static ColorMap GenericMap[] = {
965 {0, "#00d900"}, {1, "#2ad900"}, {2, "#6ed900"}, {3, "#b2d900"},
966 {4, "#d4d400"}, {5, "#d9a600"}, {7, "#d90000"}, {9, "#d90040"},
967 {12, "#d90060"}, {15, "#ae0080"}, {18, "#8300a0"}, {21, "#5700c0"},
968 {24, "#0000d0"}, {27, "#0400e0"}, {30, "#0800e0"}, {36, "#a000e0"},
969 {42, "#c004c0"}, {48, "#c008a0"}, {56, "#c0a008"}};
970
971// HTML colors taken from zygrib representation
972static ColorMap WindMap[] = {
973 {0, "#288CFF"}, {3, "#00AFFF"}, {6, "#00DCE1"}, {9, "#00F7B0"},
974 {12, "#00EA9C"}, {15, "#82F059"}, {18, "#F0F503"}, {21, "#FFED00"},
975 {24, "#FFDB00"}, {27, "#FFC700"}, {30, "#FFB400"}, {33, "#FF9800"},
976 {36, "#FF7E00"}, {39, "#F77800"}, {42, "#EC7814"}, {45, "#E4711E"},
977 {48, "#E06128"}, {51, "#DC5132"}, {54, "#D5453C"}, {57, "#CD3A46"},
978 {60, "#BE2C50"}, {63, "#B41A5A"}, {66, "#AA1464"}, {70, "#962878"},
979 {75, "#8C328C"}};
980
981// HTML colors taken from zygrib representation
982static ColorMap AirTempMap[] = {
983 {0, "#283282"}, {5, "#273c8c"}, {10, "#264696"}, {14, "#2350a0"},
984 {18, "#1f5aaa"}, {22, "#1a64b4"}, {26, "#136ec8"}, {29, "#0c78e1"},
985 {32, "#0382e6"}, {35, "#0091e6"}, {38, "#009ee1"}, {41, "#00a6dc"},
986 {44, "#00b2d7"}, {47, "#00bed2"}, {50, "#28c8c8"}, {53, "#78d2aa"},
987 {56, "#8cdc78"}, {59, "#a0eb5f"}, {62, "#c8f550"}, {65, "#f3fb02"},
988 {68, "#ffed00"}, {71, "#ffdd00"}, {74, "#ffc900"}, {78, "#ffab00"},
989 {82, "#ff8100"}, {86, "#f1780c"}, {90, "#e26a23"}, {95, "#d5453c"},
990 {100, "#b53c59"}};
991
992// Color map similar to:
993// https://www.ospo.noaa.gov/data/sst/contour/global.cf.gif
994static ColorMap SeaTempMap[] = {
995 {-2, "#cc04ae"}, {2, "#8f06e4"}, {6, "#486afa"}, {10, "#00ffff"},
996 {15, "#00d54b"}, {19, "#59d800"}, {23, "#f2fc00"}, {27, "#ff1500"},
997 {32, "#ff0000"}, {36, "#d80000"}, {40, "#a90000"}, {44, "#870000"},
998 {48, "#690000"}, {52, "#550000"}, {56, "#330000"}};
999
1000// HTML colors taken from ZyGrib representation
1001static ColorMap PrecipitationMap[] = {
1002 {0, "#ffffff"}, {.01, "#c8f0ff"}, {.02, "#b4e6ff"}, {.05, "#8cd3ff"},
1003 {.07, "#78caff"}, {.1, "#6ec1ff"}, {.2, "#64b8ff"}, {.5, "#50a6ff"},
1004 {.7, "#469eff"}, {1.0, "#3c96ff"}, {2.0, "#328eff"}, {5.0, "#1e7eff"},
1005 {7.0, "#1476f0"}, {10, "#0a6edc"}, {20, "#0064c8"}, {50, "#0052aa"}};
1006
1007// HTML colors taken from ZyGrib representation
1008static ColorMap CloudMap[] = {{0, "#ffffff"}, {1, "#f0f0e6"}, {10, "#e6e6dc"},
1009 {20, "#dcdcd2"}, {30, "#c8c8b4"}, {40, "#aaaa8c"},
1010 {50, "#969678"}, {60, "#787864"}, {70, "#646450"},
1011 {80, "#5a5a46"}, {90, "#505036"}};
1012
1013static ColorMap REFCMap[] = {{0, "#ffffff"}, {5, "#06E8E4"}, {10, "#009BE9"},
1014 {15, "#0400F3"}, {20, "#00F924"}, {25, "#06C200"},
1015 {30, "#009100"}, {35, "#FAFB00"}, {40, "#EBB608"},
1016 {45, "#FF9400"}, {50, "#FD0002"}, {55, "#D70000"},
1017 {60, "#C20300"}, {65, "#F900FE"}, {70, "#945AC8"}};
1018
1019static ColorMap CAPEMap[] = {
1020 {0, "#0046c8"}, {5, "#0050f0"}, {10, "#005aff"}, {15, "#0069ff"},
1021 {20, "#0078ff"}, {30, "#000cff"}, {45, "#00a1ff"}, {60, "#00b6fa"},
1022 {100, "#00c9ee"}, {150, "#00e0da"}, {200, "#00e6b4"}, {300, "#82e678"},
1023 {500, "#9bff3b"}, {700, "#ffdc00"}, {1000, "#ffb700"}, {1500, "#f37800"},
1024 {2000, "#d4440c"}, {2500, "#c8201c"}, {3000, "#ad0430"},
1025};
1026
1027static ColorMap WindyMap[] = {
1028 {0, "#6271B7"}, {3, "#3961A9"}, {6, "#4A94A9"}, {9, "#4D8D7B"},
1029 {12, "#53A553"}, {15, "#53A553"}, {18, "#359F35"}, {21, "#A79D51"},
1030 {24, "#9F7F3A"}, {27, "#A16C5C"}, {30, "#A16C5C"}, {33, "#813A4E"},
1031 {36, "#AF5088"}, {39, "#AF5088"}, {42, "#754A93"}, {45, "#754A93"},
1032 {48, "#6D61A3"}, {51, "#44698D"}, {54, "#44698D"}, {57, "#5C9098"},
1033 {60, "#7D44A5"}, {63, "#7D44A5"}, {66, "#7D44A5"}, {69, "#E7D7D7"},
1034 {72, "#E7D7D7"}, {75, "#E7D7D7"}, {78, "#DBD483"}, {81, "#DBD483"},
1035 {84, "#DBD483"}, {87, "#CDC470"}, {90, "#CDC470"}, {93, "#CDC470"},
1036 {96, "#CDC470"}, {99, "#808080"}};
1037
1038#if 0
1039static ColorMap *ColorMaps[] = {CurrentMap, GenericMap, WindMap, AirTempMap, SeaTempMap, PrecipitationMap, CloudMap};
1040#endif
1041
1042enum {
1043 GENERIC_GRAPHIC_INDEX,
1044 WIND_GRAPHIC_INDEX,
1045 AIRTEMP__GRAPHIC_INDEX,
1046 SEATEMP_GRAPHIC_INDEX,
1047 PRECIPITATION_GRAPHIC_INDEX,
1048 CLOUD_GRAPHIC_INDEX,
1049 CURRENT_GRAPHIC_INDEX,
1050 CAPE_GRAPHIC_INDEX,
1051 REFC_GRAPHIC_INDEX,
1052 WINDY_GRAPHIC_INDEX
1053};
1054
1055static void InitColor(ColorMap *map, size_t maplen) {
1056 wxColour c;
1057 for (size_t i = 0; i < maplen; i++) {
1058 c.Set(map[i].text);
1059 map[i].r = c.Red();
1060 map[i].g = c.Green();
1061 map[i].b = c.Blue();
1062 }
1063}
1064
1065void GRIBOverlayFactory::InitColorsTable() {
1066 InitColor(CurrentMap, (sizeof CurrentMap) / (sizeof *CurrentMap));
1067 InitColor(GenericMap, (sizeof GenericMap) / (sizeof *GenericMap));
1068 InitColor(WindMap, (sizeof WindMap) / (sizeof *WindMap));
1069 InitColor(AirTempMap, (sizeof AirTempMap) / (sizeof *AirTempMap));
1070 InitColor(SeaTempMap, (sizeof SeaTempMap) / (sizeof *SeaTempMap));
1071 InitColor(PrecipitationMap,
1072 (sizeof PrecipitationMap) / (sizeof *PrecipitationMap));
1073 InitColor(CloudMap, (sizeof CloudMap) / (sizeof *CloudMap));
1074 InitColor(CAPEMap, (sizeof CAPEMap) / (sizeof *CAPEMap));
1075 InitColor(REFCMap, (sizeof REFCMap) / (sizeof *REFCMap));
1076 InitColor(WindyMap, (sizeof WindyMap) / (sizeof *WindyMap));
1077}
1078
1079void GRIBOverlayFactory::GetGraphicColor(int settings, double val_in,
1080 unsigned char &r, unsigned char &g,
1081 unsigned char &b) {
1082 int colormap_index = m_Settings.Settings[settings].m_iOverlayMapColors;
1083 ColorMap *map;
1084 int maplen;
1085
1086 /* normalize input value */
1087 double min = m_Settings.GetMin(settings), max = m_Settings.GetMax(settings);
1088
1089 val_in -= min;
1090 val_in /= max - min;
1091
1092 switch (colormap_index) {
1093 case CURRENT_GRAPHIC_INDEX:
1094 map = CurrentMap;
1095 maplen = (sizeof CurrentMap) / (sizeof *CurrentMap);
1096 break;
1097 case GENERIC_GRAPHIC_INDEX:
1098 map = GenericMap;
1099 maplen = (sizeof GenericMap) / (sizeof *GenericMap);
1100 break;
1101 case WIND_GRAPHIC_INDEX:
1102 map = WindMap;
1103 maplen = (sizeof WindMap) / (sizeof *WindMap);
1104 break;
1105 case AIRTEMP__GRAPHIC_INDEX:
1106 map = AirTempMap;
1107 maplen = (sizeof AirTempMap) / (sizeof *AirTempMap);
1108 break;
1109 case SEATEMP_GRAPHIC_INDEX:
1110 map = SeaTempMap;
1111 maplen = (sizeof SeaTempMap) / (sizeof *SeaTempMap);
1112 break;
1113 case PRECIPITATION_GRAPHIC_INDEX:
1114 map = PrecipitationMap;
1115 maplen = (sizeof PrecipitationMap) / (sizeof *PrecipitationMap);
1116 break;
1117 case CLOUD_GRAPHIC_INDEX:
1118 map = CloudMap;
1119 maplen = (sizeof CloudMap) / (sizeof *CloudMap);
1120 break;
1121 case CAPE_GRAPHIC_INDEX:
1122 map = CAPEMap;
1123 maplen = (sizeof CAPEMap) / (sizeof *CAPEMap);
1124 break;
1125 case REFC_GRAPHIC_INDEX:
1126 map = REFCMap;
1127 maplen = (sizeof REFCMap) / (sizeof *REFCMap);
1128 break;
1129 case WINDY_GRAPHIC_INDEX:
1130 map = WindyMap;
1131 maplen = (sizeof WindyMap) / (sizeof *WindyMap);
1132 break;
1133 default:
1134 return;
1135 }
1136
1137 /* normalize map from 0 to 1 */
1138 double cmax = map[maplen - 1].val;
1139
1140 for (int i = 1; i < maplen; i++) {
1141 double nmapvala = map[i - 1].val / cmax;
1142 double nmapvalb = map[i].val / cmax;
1143 if (nmapvalb > val_in || i == maplen - 1) {
1144 if (m_bGradualColors) {
1145 double d = (val_in - nmapvala) / (nmapvalb - nmapvala);
1146 r = (1 - d) * map[i - 1].r + d * map[i].r;
1147 g = (1 - d) * map[i - 1].g + d * map[i].g;
1148 b = (1 - d) * map[i - 1].b + d * map[i].b;
1149 } else {
1150 r = map[i].r;
1151 g = map[i].g;
1152 b = map[i].b;
1153 }
1154 return;
1155 }
1156 }
1157 /* unreachable */
1158}
1159
1160wxColour GRIBOverlayFactory::GetGraphicColor(int settings, double val_in) {
1161 unsigned char r, g, b;
1162 GetGraphicColor(settings, val_in, r, g, b);
1163 return wxColour(r, g, b);
1164}
1165
1166wxString GRIBOverlayFactory::getLabelString(double value, int settings) {
1167 int p;
1168 wxString f = "%.*f";
1169
1170 switch (settings) {
1171 case GribOverlaySettings::PRESSURE: /* 2 */
1172 p = 0;
1173 if (m_Settings.Settings[settings].m_Units == 2)
1174 p = 2;
1175 else if (m_Settings.Settings[settings].m_Units == 0 &&
1176 m_Settings.Settings[settings].m_bAbbrIsoBarsNumbers) {
1177 value -= floor(value / 100.) * 100.;
1178 f = "%02.*f";
1179 }
1180 break;
1181 case GribOverlaySettings::WAVE: /* 3 */
1182 case GribOverlaySettings::CURRENT: /* 4 */
1183 case GribOverlaySettings::AIR_TEMPERATURE: /* 7 */
1184 case GribOverlaySettings::SEA_TEMPERATURE: /* 8 */
1185 p = 1;
1186 break;
1187 case GribOverlaySettings::PRECIPITATION: /* 5 */
1188 p = value < 100. ? 2 : value < 10. ? 1 : 0;
1189 p += m_Settings.Settings[settings].m_Units == 1 ? 1 : 0;
1190 break;
1191 default:
1192 p = 0;
1193 }
1194 return wxString::Format(f, p, value);
1195}
1196
1197/* return cached wxImage for a given number, or create it if not in the cache */
1198wxImage &GRIBOverlayFactory::getLabel(double value, int settings,
1199 wxColour back_color) {
1200 std::map<double, wxImage>::iterator it;
1201 it = m_labelCache.find(value);
1202 if (it != m_labelCache.end()) return m_labelCache[value];
1203
1204 wxString labels = getLabelString(value, settings);
1205
1206 wxColour text_color;
1207 GetGlobalColor(_T ( "UBLCK" ), &text_color);
1208 wxPen penText(text_color);
1209
1210 wxBrush backBrush(back_color);
1211
1212 wxFont mfont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
1213 wxFONTWEIGHT_NORMAL);
1214
1215 wxScreenDC sdc;
1216 int w, h;
1217 sdc.GetTextExtent(labels, &w, &h, nullptr, nullptr, &mfont);
1218
1219 int label_offset = 5;
1220
1221 wxBitmap bm(w + label_offset * 2, h + 2);
1222 wxMemoryDC mdc(bm);
1223 mdc.Clear();
1224
1225 mdc.SetFont(mfont);
1226 mdc.SetPen(penText);
1227 mdc.SetBrush(backBrush);
1228 mdc.SetTextForeground(text_color);
1229 mdc.SetTextBackground(back_color);
1230
1231 int xd = 0;
1232 int yd = 0;
1233 // mdc.DrawRoundedRectangle(xd, yd, w+(label_offset * 2), h+2, -.25);
1234 mdc.DrawRectangle(xd, yd, w + (label_offset * 2), h + 2);
1235 mdc.DrawText(labels, label_offset + xd, yd + 1);
1236
1237 mdc.SelectObject(wxNullBitmap);
1238
1239 m_labelCache[value] = bm.ConvertToImage();
1240
1241 m_labelCache[value].InitAlpha();
1242
1243 return m_labelCache[value];
1244}
1245
1246double square(double x) { return x * x; }
1247
1248void GRIBOverlayFactory::RenderGribBarbedArrows(int settings, GribRecord **pGR,
1249 PlugIn_ViewPort *vp) {
1250 if (!m_Settings.Settings[settings].m_bBarbedArrows) return;
1251
1252 // Need two records to draw the barbed arrows
1253 GribRecord *pGRX, *pGRY;
1254 int idx, idy;
1255 bool polar;
1256 SettingsIdToGribId(settings, idx, idy, polar);
1257 if (idx < 0 || idy < 0) return;
1258
1259 pGRX = pGR[idx];
1260 pGRY = pGR[idy];
1261
1262 if (!pGRX || !pGRY) return;
1263
1264 wxColour colour;
1265 GetGlobalColor(_T ( "YELO2" ), &colour);
1266
1267#ifdef ocpnUSE_GL
1268 if (!m_pdc) {
1269#ifndef __OCPN__ANDROID__
1270 // Enable anti-aliased lines, at best quality
1271 glEnable(GL_LINE_SMOOTH);
1272 glEnable(GL_BLEND);
1273 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1274 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
1275 glLineWidth(2);
1276#else
1277 glLineWidth(5); // 5 pixels for dense displays
1278#endif
1279
1280 glEnableClientState(GL_VERTEX_ARRAY);
1281 }
1282#endif
1283
1284 if (m_Settings.Settings[settings].m_bBarbArrFixSpac) {
1285 // Get spacing in pixels from settings
1286 int space_pixels =
1287 adjustSpacing(m_Settings.Settings[settings].m_iBarbArrSpacing);
1288 int arrowSize = 16;
1289 int total_spacing = space_pixels + arrowSize; // Physical pixels.
1290
1291 // Convert pixel spacing to geographic spacing
1292 // We need to create a reference point and move it by the spacing to find
1293 // the geo difference
1294 wxPoint center(vp->pix_width / 2, vp->pix_height / 2);
1295 double center_lat, center_lon;
1296 GetCanvasLLPix(vp, center, &center_lat, &center_lon);
1297
1298 // Find lat/lon of a point offset by total_spacing
1299 wxPoint offset_point(center.x + total_spacing, center.y + total_spacing);
1300 double offset_lat, offset_lon;
1301 GetCanvasLLPix(vp, offset_point, &offset_lat, &offset_lon);
1302
1303 // Calculate spacing in geographic coordinates
1304 double lat_spacing = fabs(center_lat - offset_lat);
1305 double lon_spacing = fabs(center_lon - offset_lon);
1306
1307 // Generate grid in geographic coordinates
1308 // Find grid origin that aligns with whole-number multiples of spacing
1309 double start_lat = floor(vp->lat_min / lat_spacing) * lat_spacing;
1310 double start_lon = floor(vp->lon_min / lon_spacing) * lon_spacing;
1311
1312 // Expand bounds slightly to ensure we cover the viewport edges
1313 double end_lat = vp->lat_max + lat_spacing;
1314 double end_lon = vp->lon_max + lon_spacing;
1315
1316 // Draw grid of arrows based on geographical coordinates
1317 for (double lat = start_lat; lat <= end_lat; lat += lat_spacing) {
1318 for (double lon = start_lon; lon <= end_lon; lon += lon_spacing) {
1319 // Convert geographic point to screen coordinates
1320 wxPoint p;
1321 GetCanvasPixLL(vp, &p, lat, lon);
1322
1323 // Get data value at this location
1324 double vkn, ang;
1325 if (GribRecord::getInterpolatedValues(vkn, ang, pGRX, pGRY, lon, lat)) {
1326 drawWindArrowWithBarbs(settings, p.x, p.y, vkn * 3.6 / 1.852,
1327 (ang - 90) * M_PI / 180, (lat < 0.), colour,
1328 vp->rotation);
1329 }
1330 }
1331 }
1332 } else {
1333 // set minimum spacing between arrows
1334 double minspace = wxMax(m_Settings.Settings[settings].m_iBarbArrSpacing,
1335 windArrowSize * 1.2);
1336 double minspace2 = square(minspace);
1337
1338 // Get the the grid
1339 int imax = pGRX->getNi(); // Longitude
1340 int jmax = pGRX->getNj(); // Latitude
1341
1342 wxPoint firstpx(-1000, -1000);
1343 wxPoint oldpx(-1000, -1000);
1344 wxPoint oldpy(-1000, -1000);
1345
1346 for (int i = 0; i < imax; i++) {
1347 double lonl, latl;
1348
1349 /* at midpoint of grib so as to avoid problems in projection on
1350 gribs that go all the way to the north or south pole */
1351 pGRX->getXY(i, pGRX->getNj() / 2, &lonl, &latl);
1352 wxPoint pl;
1353 GetCanvasPixLL(vp, &pl, latl, lonl);
1354
1355 if (pl.x <= firstpx.x &&
1356 square(pl.x - firstpx.x) + square(pl.y - firstpx.y) <
1357 minspace2 / 1.44)
1358 continue;
1359
1360 if (square(pl.x - oldpx.x) + square(pl.y - oldpx.y) < minspace2) continue;
1361
1362 oldpx = pl;
1363 if (i == 0) firstpx = pl;
1364
1365 double lon = lonl;
1366 for (int j = 0; j < jmax; j++) {
1367 double lat = pGRX->getY(j);
1368
1369 if (!PointInLLBox(vp, lon, lat)) continue;
1370
1371 wxPoint p;
1372 GetCanvasPixLL(vp, &p, lat, lon);
1373
1374 if (square(p.x - oldpy.x) + square(p.y - oldpy.y) < minspace2) continue;
1375
1376 oldpy = p;
1377
1378 if (lon > 180) lon -= 360;
1379
1380 double vx = pGRX->getValue(i, j);
1381 double vy = pGRY->getValue(i, j);
1382
1383 if (vx != GRIB_NOTDEF && vy != GRIB_NOTDEF) {
1384 double vkn, ang;
1385 vkn = sqrt(vx * vx + vy * vy);
1386 ang = atan2(vy, -vx);
1387 drawWindArrowWithBarbs(settings, p.x, p.y, vkn * 3.6 / 1.852, ang,
1388 (lat < 0.), colour, vp->rotation);
1389 }
1390 }
1391 }
1392 }
1393
1394#ifdef ocpnUSE_GL
1395 if (!m_pdc) glDisableClientState(GL_VERTEX_ARRAY);
1396#endif
1397}
1398
1399void GRIBOverlayFactory::RenderGribIsobar(int settings, GribRecord **pGR,
1400 wxArrayPtrVoid **pIsobarArray,
1401 PlugIn_ViewPort *vp) {
1402 if (!m_Settings.Settings[settings].m_bIsoBars) return;
1403
1404 // Need magnitude to draw isobars
1405 int idx, idy;
1406 bool polar;
1407 SettingsIdToGribId(settings, idx, idy, polar);
1408 if (idx < 0) return;
1409
1410 GribRecord *pGRA = pGR[idx], *pGRM = nullptr;
1411
1412 if (!pGRA) return;
1413
1414 wxColour back_color;
1415 GetGlobalColor(_T ( "DILG1" ), &back_color);
1416
1417 // Initialize the array of Isobars if necessary
1418 if (!pIsobarArray[idx]) {
1419 // build magnitude from multiple record types like wind and current
1420 if (idy >= 0 && !polar && pGR[idy]) {
1421 pGRM = GribRecord::MagnitudeRecord(*pGR[idx], *pGR[idy]);
1422 if (!pGRM->isOk()) {
1423 m_Message_Hiden.Append(_("IsoBar Unable to compute record magnitude"));
1424 delete pGRM;
1425 return;
1426 }
1427 pGRA = pGRM;
1428 }
1429
1430 pIsobarArray[idx] = new wxArrayPtrVoid;
1431 IsoLine *piso;
1432
1433 wxGenericProgressDialog *progressdialog = nullptr;
1434 wxDateTime start = wxDateTime::Now();
1435
1436 double min = m_Settings.GetMin(settings);
1437 double max = m_Settings.GetMax(settings);
1438
1439 /* convert min and max to units being used */
1440 double factor = (settings == GribOverlaySettings::PRESSURE &&
1441 m_Settings.Settings[settings].m_Units == 2)
1442 ? 0.03
1443 : 1.; // divide spacing by 1/33 for PRESURRE & inHG
1444
1445 for (double press = min; press <= max;
1446 press += (m_Settings.Settings[settings].m_iIsoBarSpacing * factor)) {
1447 if (progressdialog)
1448 progressdialog->Update(press - min);
1449 else {
1450 wxDateTime now = wxDateTime::Now();
1451 if ((now - start).GetSeconds() > 3 && press - min < (max - min) / 2) {
1452 progressdialog = new wxGenericProgressDialog(
1453 _("Building Isobar map"), _("Wind"), max - min + 1, nullptr,
1454 wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME);
1455 }
1456 }
1457
1458 piso = new IsoLine(press,
1459 m_Settings.CalibrationFactor(settings, press, true),
1460 m_Settings.CalibrationOffset(settings), pGRA);
1461
1462 pIsobarArray[idx]->Add(piso);
1463 }
1464 delete progressdialog;
1465
1466 delete pGRM;
1467 }
1468
1469 // Draw the Isobars
1470 for (unsigned int i = 0; i < pIsobarArray[idx]->GetCount(); i++) {
1471 IsoLine *piso = (IsoLine *)pIsobarArray[idx]->Item(i);
1472 piso->drawIsoLine(this, m_pdc, vp, true); // g_bGRIBUseHiDef
1473
1474 // Draw Isobar labels
1475
1476 int density = 40;
1477 int first = 0;
1478 if (m_pdc)
1479 piso->drawIsoLineLabels(this, m_pdc, vp, density, first,
1480 getLabel(piso->getValue(), settings, back_color));
1481 else
1482 piso->drawIsoLineLabelsGL(this, vp, density, first,
1483 getLabelString(piso->getValue(), settings),
1484 back_color, m_TexFontNumbers);
1485 }
1486}
1487
1488void GRIBOverlayFactory::FillGrid(GribRecord *pGR) {
1489 // Get the the grid
1490 int imax = pGR->getNi(); // Longitude
1491 int jmax = pGR->getNj(); // Latitude
1492
1493 for (int i = 0; i < imax; i++) {
1494 for (int j = 1; j < jmax - 1; j++) {
1495 if (pGR->getValue(i, j) == GRIB_NOTDEF) {
1496 double acc = 0;
1497 double div = 0;
1498 if (pGR->getValue(i, j - 1) != GRIB_NOTDEF) {
1499 acc += pGR->getValue(i, j - 1);
1500 div += 1;
1501 }
1502 if (pGR->getValue(i, j + 1) != GRIB_NOTDEF) {
1503 acc += pGR->getValue(i, j + 1);
1504 div += 1;
1505 }
1506 if (div > 1) pGR->setValue(i, j, acc / div);
1507 }
1508 }
1509 }
1510
1511 for (int j = 0; j < jmax; j++) {
1512 for (int i = 1; i < imax - 1; i++) {
1513 if (pGR->getValue(i, j) == GRIB_NOTDEF) {
1514 double acc = 0;
1515 double div = 0;
1516 if (pGR->getValue(i - 1, j) != GRIB_NOTDEF) {
1517 acc += pGR->getValue(i - 1, j);
1518 div += 1;
1519 }
1520 if (pGR->getValue(i + 1, j) != GRIB_NOTDEF) {
1521 acc += pGR->getValue(i + 1, j);
1522 div += 1;
1523 }
1524 if (div > 1) pGR->setValue(i, j, acc / div);
1525 }
1526 }
1527 }
1528
1529 pGR->setFilled(true);
1530}
1531
1532void GRIBOverlayFactory::RenderGribDirectionArrows(int settings,
1533 GribRecord **pGR,
1534 PlugIn_ViewPort *vp) {
1535 if (!m_Settings.Settings[settings].m_bDirectionArrows) return;
1536 // need two records or a polar record to draw arrows
1537 GribRecord *pGRX, *pGRY;
1538 int idx, idy;
1539 bool polar;
1540 SettingsIdToGribId(settings, idx, idy, polar);
1541 if (idx < 0 || idy < 0) return;
1542
1543 pGRX = pGR[idx];
1544 pGRY = pGR[idy];
1545 if (!pGRX || !pGRY) return;
1546 if (!pGRX->isFilled()) FillGrid(pGRX);
1547 if (!pGRY->isFilled()) FillGrid(pGRY);
1548
1549 // Set arrows Size
1550 int arrowWidth = 2;
1551 int arrowSize,
1552 arrowSizeIdx = m_Settings.Settings[settings].m_iDirectionArrowSize;
1553 if (arrowSizeIdx == 0) {
1554 if (m_pixelMM > 0.2)
1555 arrowSize = 26;
1556 else
1557 arrowSize = 5. / m_pixelMM;
1558 } else
1559 arrowSize = 16;
1560
1561 // set default colour
1562 wxColour colour;
1563 GetGlobalColor(_T ( "DILG3" ), &colour);
1564
1565#ifdef ocpnUSE_GL
1566 if (!m_pdc) {
1567 if (m_pixelMM > 0.2) {
1568 // Enable anti-aliased lines, at best quality
1569 glEnable(GL_LINE_SMOOTH);
1570 glEnable(GL_BLEND);
1571 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1572 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
1573 } else {
1574 if (m_Settings.Settings[settings].m_iDirectionArrowForm == 0) // Single?
1575 arrowWidth = 4;
1576 else
1577 arrowWidth = 3;
1578 }
1579
1580 glEnableClientState(GL_VERTEX_ARRAY);
1581 }
1582#endif
1583
1584 if (m_Settings.Settings[settings].m_bDirArrFixSpac) { // fixed spacing
1585 // Get spacing in pixels from settings
1586 int space_pixels =
1587 adjustSpacing(m_Settings.Settings[settings].m_iBarbArrSpacing);
1588 int arrowSize = 16;
1589 int total_spacing = space_pixels + arrowSize; // Physical pixels.
1590
1591 // Convert pixel spacing to geographic spacing
1592 // We need to create a reference point and move it by the spacing to find
1593 // the geo difference
1594 wxPoint center(vp->pix_width / 2, vp->pix_height / 2);
1595 double center_lat, center_lon;
1596 GetCanvasLLPix(vp, center, &center_lat, &center_lon);
1597
1598 // Find lat/lon of a point offset by total_spacing
1599 wxPoint offset_point(center.x + total_spacing, center.y + total_spacing);
1600 double offset_lat, offset_lon;
1601 GetCanvasLLPix(vp, offset_point, &offset_lat, &offset_lon);
1602
1603 // Calculate spacing in geographic coordinates
1604 double lat_spacing = fabs(center_lat - offset_lat);
1605 double lon_spacing = fabs(center_lon - offset_lon);
1606
1607 // Generate grid in geographic coordinates
1608 // Find grid origin that aligns with whole-number multiples of spacing
1609 double start_lat = floor(vp->lat_min / lat_spacing) * lat_spacing;
1610 double start_lon = floor(vp->lon_min / lon_spacing) * lon_spacing;
1611
1612 // Expand bounds slightly to ensure we cover the viewport edges
1613 double end_lat = vp->lat_max + lat_spacing;
1614 double end_lon = vp->lon_max + lon_spacing;
1615
1616 // Draw grid of arrows based on geographical coordinates
1617 for (double lat = start_lat; lat <= end_lat; lat += lat_spacing) {
1618 for (double lon = start_lon; lon <= end_lon; lon += lon_spacing) {
1619 // Convert geographic point to screen coordinates
1620 wxPoint p;
1621 GetCanvasPixLL(vp, &p, lat, lon);
1622
1623 double sh, dir;
1624 double scale = 1.0;
1625
1626 if (polar) { // wave arrows
1627 sh = pGRX->getInterpolatedValue(lon, lat, true);
1628 dir = pGRY->getInterpolatedValue(lon, lat, true, true);
1629
1630 if (dir == GRIB_NOTDEF || sh == GRIB_NOTDEF) continue;
1631 } else { // current arrows
1632 if (!GribRecord::getInterpolatedValues(sh, dir, pGRX, pGRY, lon, lat))
1633 continue;
1634 scale = wxMax(1.0, sh); // Size depends on magnitude.
1635 }
1636
1637 dir = (dir - 90) * M_PI / 180.;
1638
1639 // draw arrows
1640 if (m_Settings.Settings[settings].m_iDirectionArrowForm == 0)
1641 drawSingleArrow(p.x, p.y, dir + vp->rotation, colour, arrowWidth,
1642 arrowSizeIdx, scale);
1643 else if (m_Settings.Settings[settings].m_iDirectionArrowForm == 1)
1644 drawDoubleArrow(p.x, p.y, dir + vp->rotation, colour, arrowWidth,
1645 arrowSizeIdx, scale);
1646 else
1647 drawSingleArrow(p.x, p.y, dir + vp->rotation, colour,
1648 wxMax(1, wxMin(8, (int)(sh + 0.5))), arrowSizeIdx,
1649 scale);
1650 }
1651 }
1652
1653 } else { // end fixed spacing -> minimum spacing
1654
1655 // set minimum spacing between arrows
1656 double minspace =
1657 wxMax(m_Settings.Settings[settings].m_iDirArrSpacing,
1658 m_Settings.Settings[settings].m_iDirectionArrowSize * 1.2);
1659 double minspace2 = square(minspace);
1660
1661 // Get the the grid
1662 int imax = pGRX->getNi(); // Longitude
1663 int jmax = pGRX->getNj(); // Latitude
1664
1665 wxPoint firstpx(-1000, -1000);
1666 wxPoint oldpx(-1000, -1000);
1667 wxPoint oldpy(-1000, -1000);
1668
1669 for (int i = 0; i < imax; i++) {
1670 double lonl, latl;
1671 pGRX->getXY(i, pGRX->getNj() / 2, &lonl, &latl);
1672
1673 wxPoint pl;
1674 GetCanvasPixLL(vp, &pl, latl, lonl);
1675
1676 if (pl.x <= firstpx.x &&
1677 square(pl.x - firstpx.x) + square(pl.y - firstpx.y) <
1678 minspace2 / 1.44)
1679 continue;
1680
1681 if (square(pl.x - oldpx.x) + square(pl.y - oldpx.y) < minspace2) continue;
1682
1683 oldpx = pl;
1684 if (i == 0) firstpx = pl;
1685
1686 for (int j = 0; j < jmax; j++) {
1687 double lon, lat;
1688 pGRX->getXY(i, j, &lon, &lat);
1689
1690 wxPoint p;
1691 GetCanvasPixLL(vp, &p, lat, lon);
1692
1693 if (square(p.x - oldpy.x) + square(p.y - oldpy.y) >= minspace2) {
1694 oldpy = p;
1695
1696 if (lon > 180) lon -= 360;
1697
1698 if (PointInLLBox(vp, lon, lat)) {
1699 double sh, dir, wdh;
1700 double scale = 1.0;
1701 if (polar) { // wave arrows
1702 dir = pGRY->getValue(i, j);
1703 sh = pGRX->getValue(i, j);
1704
1705 if (dir == GRIB_NOTDEF || sh == GRIB_NOTDEF) continue;
1706
1707 wdh = sh + 0.5;
1708 } else {
1709 if (!GribRecord::getInterpolatedValues(sh, dir, pGRX, pGRY, lon,
1710 lat, false))
1711 continue;
1712
1713 wdh = (8 / 2.5 * sh) + 0.5;
1714 scale = wxMax(1.0, sh); // Size depends on magnitude.
1715 }
1716
1717 dir = (dir - 90) * M_PI / 180.;
1718
1719 // draw arrows
1720 if (m_Settings.Settings[settings].m_iDirectionArrowForm == 0)
1721 drawSingleArrow(p.x, p.y, dir + vp->rotation, colour, arrowWidth,
1722 arrowSizeIdx, scale);
1723 else if (m_Settings.Settings[settings].m_iDirectionArrowForm == 1)
1724 drawDoubleArrow(p.x, p.y, dir + vp->rotation, colour, arrowWidth,
1725 arrowSizeIdx, scale);
1726 else
1727 drawSingleArrow(p.x, p.y, dir + vp->rotation, colour,
1728 wxMax(1, wxMin(8, (int)wdh)), arrowSizeIdx,
1729 scale);
1730 }
1731 }
1732 }
1733 }
1734 }
1735
1736#ifdef ocpnUSE_GL
1737 if (!m_pdc) glDisableClientState(GL_VERTEX_ARRAY);
1738#endif
1739}
1740
1741void GRIBOverlayFactory::RenderGribOverlayMap(int settings, GribRecord **pGR,
1742 PlugIn_ViewPort *vp) {
1743 if (!m_Settings.Settings[settings].m_bOverlayMap) return;
1744
1745 const int grib_pixel_size = 4;
1746 bool polar;
1747 int idx, idy;
1748 SettingsIdToGribId(settings, idx, idy, polar);
1749 if (idx < 0 || !pGR[idx]) return;
1750
1751 GribRecord *pGRA = pGR[idx], *pGRM = nullptr;
1752 if (!pGRA) return;
1753
1754 if (idy >= 0 && !polar && pGR[idy]) {
1755 pGRM = GribRecord::MagnitudeRecord(*pGR[idx], *pGR[idy]);
1756 if (!pGRM->isOk()) {
1757 m_Message_Hiden.Append(
1758 _("OverlayMap Unable to compute record magnitude"));
1759 delete pGRM;
1760 return;
1761 }
1762 pGRA = pGRM;
1763 }
1764
1765 if (!pGRA->isFilled()) FillGrid(pGRA);
1766
1767 wxPoint porg;
1768 GetCanvasPixLL(vp, &porg, pGRA->getLatMax(), pGRA->getLonMin());
1769
1770 // Check two BBoxes....
1771 // TODO Make a better Intersect method
1772 bool bdraw = false;
1773 if (Intersect(vp, pGRA->getLatMin(), pGRA->getLatMax(), pGRA->getLonMin(),
1774 pGRA->getLonMax(), 0.) != _GOUT)
1775 bdraw = true;
1776 if (Intersect(vp, pGRA->getLatMin(), pGRA->getLatMax(),
1777 pGRA->getLonMin() - 360., pGRA->getLonMax() - 360.,
1778 0.) != _GOUT)
1779 bdraw = true;
1780
1781 if (bdraw) {
1782 // If needed, create the overlay
1783 if (!m_pOverlay[settings]) m_pOverlay[settings] = new GribOverlay;
1784
1785 GribOverlay *pGO = m_pOverlay[settings];
1786
1787 if (!m_pdc) // OpenGL mode
1788 {
1789#ifdef ocpnUSE_GL
1790
1791 texture_format = GL_TEXTURE_2D;
1792
1793 if (!texture_format) // it's very unlikely to not have any of the above
1794 // extensions
1795 m_Message_Hiden.Append(
1796 _("Overlays not supported by this graphics hardware (Disable "
1797 "OpenGL)"));
1798 else {
1799 if (!pGO->m_iTexture) CreateGribGLTexture(pGO, settings, pGRA);
1800
1801 if (pGO->m_iTexture)
1802 DrawGLTexture(pGO, pGRA, vp);
1803 else
1804 m_Message_Hiden.IsEmpty()
1805 ? m_Message_Hiden
1806 .Append(_("Overlays too wide and can't be displayed:"))
1807 .Append(" ")
1808 .Append(GribOverlaySettings::NameFromIndex(settings))
1809 : m_Message_Hiden.Append(",").Append(
1810 GribOverlaySettings::NameFromIndex(settings));
1811 }
1812#endif
1813 } else // DC mode
1814 {
1815 if (fabs(vp->rotation) > 0.1) {
1816 m_Message_Hiden.Append(_(
1817 "overlays suppressed if not north-up in DC mode (enable OpenGL)"));
1818 } else {
1819 if (!pGO->m_pDCBitmap) {
1820 wxImage bl_image =
1821 CreateGribImage(settings, pGRA, vp, grib_pixel_size, porg);
1822 if (bl_image.IsOk()) {
1823 // Create a Bitmap
1824 pGO->m_pDCBitmap = new wxBitmap(bl_image);
1825 wxMask *gr_mask =
1826 new wxMask(*(pGO->m_pDCBitmap), wxColour(0, 0, 0));
1827 pGO->m_pDCBitmap->SetMask(gr_mask);
1828 }
1829 }
1830
1831 if (pGO->m_pDCBitmap)
1832 m_pdc->DrawBitmap(*(pGO->m_pDCBitmap), porg.x, porg.y, true);
1833 else
1834 m_Message_Hiden.IsEmpty()
1835 ? m_Message_Hiden
1836 .Append(_(
1837 "Please Zoom or Scale Out to view invisible overlays:"))
1838 .Append(" ")
1839 .Append(GribOverlaySettings::NameFromIndex(settings))
1840 : m_Message_Hiden.Append(",").Append(
1841 GribOverlaySettings::NameFromIndex(settings));
1842 }
1843 }
1844 }
1845
1846 delete pGRM;
1847}
1848
1849void GRIBOverlayFactory::RenderGribNumbers(int settings, GribRecord **pGR,
1850 PlugIn_ViewPort *vp) {
1851 if (!m_Settings.Settings[settings].m_bNumbers) return;
1852
1853 // Need magnitude to draw numbers
1854 int idx, idy;
1855 bool polar;
1856 SettingsIdToGribId(settings, idx, idy, polar);
1857 if (idx < 0) return;
1858
1859 GribRecord *pGRA = pGR[idx], *pGRM = nullptr;
1860
1861 if (!pGRA) return;
1862
1863 /* build magnitude from multiple record types like wind and current */
1864 if (idy >= 0 && !polar && pGR[idy]) {
1865 pGRM = GribRecord::MagnitudeRecord(*pGR[idx], *pGR[idy]);
1866 if (!pGRM->isOk()) {
1867 m_Message_Hiden.Append(
1868 _("GribNumbers Unable to compute record magnitude"));
1869 delete pGRM;
1870 return;
1871 }
1872 pGRA = pGRM;
1873 }
1874
1875 // set an arbitrary width for numbers
1876 int wstring;
1877 m_TexFontNumbers.GetTextExtent(wxString("1234"), &wstring, nullptr);
1878
1879 if (m_Settings.Settings[settings].m_bNumFixSpac) { // fixed spacing
1880
1881 // Set spacing between numbers
1882 int space = adjustSpacing(m_Settings.Settings[settings].m_iNumbersSpacing);
1883
1884 PlugIn_ViewPort uvp = *vp;
1885 uvp.rotation = uvp.skew = 0;
1886
1887 wxPoint ptl, pbr;
1888 GetCanvasPixLL(&uvp, &ptl, wxMin(pGRA->getLatMax(), 89.0),
1889 pGRA->getLonMin()); // top left corner position
1890 GetCanvasPixLL(&uvp, &pbr, wxMax(pGRA->getLatMin(), -89.0),
1891 pGRA->getLonMax()); // bottom right corner position
1892 if (ptl.x >= pbr.x) {
1893 // 360
1894 ptl.x = 0;
1895 pbr.x = m_ParentSize.GetWidth();
1896 }
1897
1898 for (int i = wxMax(ptl.x, 0); i < wxMin(pbr.x, m_ParentSize.GetWidth());
1899 i += (space + wstring)) {
1900 for (int j = wxMax(ptl.y, 0); j < wxMin(pbr.y, m_ParentSize.GetHeight());
1901 j += (space + wstring)) {
1902 double lat, lon, val;
1903 GetCanvasLLPix(vp, wxPoint(i, j), &lat, &lon);
1904 val = pGRA->getInterpolatedValue(lon, lat, true);
1905 if (val != GRIB_NOTDEF) {
1906 double value = m_Settings.CalibrateValue(settings, val);
1907 wxColour back_color = GetGraphicColor(settings, value);
1908
1909 DrawNumbers(wxPoint(i, j), value, settings, back_color);
1910 }
1911 }
1912 }
1913 } else {
1914 // set minimum spacing between arrows
1915 double minspace =
1916 wxMax(m_Settings.Settings[settings].m_iNumbersSpacing, wstring * 1.2);
1917 double minspace2 = square(minspace);
1918
1919 // Get the the grid
1920 int imax = pGRA->getNi(); // Longitude
1921 int jmax = pGRA->getNj(); // Latitude
1922
1923 wxPoint firstpx(-1000, -1000);
1924 wxPoint oldpx(-1000, -1000);
1925 wxPoint oldpy(-1000, -1000);
1926
1927 for (int i = 0; i < imax; i++) {
1928 double lonl, latl;
1929 pGRA->getXY(i, pGRA->getNj() / 2, &lonl, &latl);
1930
1931 wxPoint pl;
1932 GetCanvasPixLL(vp, &pl, latl, lonl);
1933
1934 if (pl.x <= firstpx.x &&
1935 square(pl.x - firstpx.x) + square(pl.y - firstpx.y) <
1936 minspace2 / 1.44)
1937 continue;
1938
1939 if (square(pl.x - oldpx.x) + square(pl.y - oldpx.y) >= minspace2) {
1940 oldpx = pl;
1941 if (i == 0) firstpx = pl;
1942
1943 for (int j = 0; j < jmax; j++) {
1944 double lon, lat;
1945 pGRA->getXY(i, j, &lon, &lat);
1946
1947 wxPoint p;
1948 GetCanvasPixLL(vp, &p, lat, lon);
1949
1950 if (square(p.x - oldpy.x) + square(p.y - oldpy.y) >= minspace2) {
1951 oldpy = p;
1952
1953 if (lon > 180) lon -= 360;
1954
1955 if (PointInLLBox(vp, lon, lat)) {
1956 double mag = pGRA->getValue(i, j);
1957
1958 if (mag != GRIB_NOTDEF) {
1959 double value = m_Settings.CalibrateValue(settings, mag);
1960 wxColour back_color = GetGraphicColor(settings, value);
1961
1962 DrawNumbers(p, value, settings, back_color);
1963 }
1964 }
1965 }
1966 }
1967 }
1968 }
1969 }
1970
1971 delete pGRM;
1972}
1973
1974void GRIBOverlayFactory::DrawNumbers(wxPoint p, double value, int settings,
1975 wxColour back_color) {
1976 if (m_pdc) {
1977 wxImage &label = getLabel(value, settings, back_color);
1978 // set alpha chanel
1979 int w = label.GetWidth(), h = label.GetHeight();
1980 for (int y = 0; y < h; y++)
1981 for (int x = 0; x < w; x++)
1982 label.SetAlpha(x, y, m_Settings.m_iOverlayTransparency);
1983
1984 m_pdc->DrawBitmap(label, p.x, p.y, true);
1985 } else {
1986#ifdef ocpnUSE_GL
1987#if 0 // ndef USE_ANDROID_GLES2
1988
1989 glEnable(GL_BLEND);
1990 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1991 glColor4ub(back_color.Red(), back_color.Green(), back_color.Blue(),
1992 m_Settings.m_iOverlayTransparency);
1993
1994 glLineWidth(1);
1995
1996 wxString label = getLabelString(value, settings);
1997 int w, h;
1998 m_TexFontNumbers.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 /* draw bounding rectangle */
2005 glBegin(GL_QUADS);
2006 glVertex2i(x, y);
2007 glVertex2i(x + w, y);
2008 glVertex2i(x + w, y + h);
2009 glVertex2i(x, y + h);
2010 glEnd();
2011
2012 glColor4ub(0, 0, 0, m_Settings.m_iOverlayTransparency);
2013
2014 glBegin(GL_LINE_LOOP);
2015 glVertex2i(x, y);
2016 glVertex2i(x + w, y);
2017 glVertex2i(x + w, y + h);
2018 glVertex2i(x, y + h);
2019 glEnd();
2020
2021 glEnable(GL_TEXTURE_2D);
2022 m_TexFontNumbers.RenderString(label, p.x, p.y);
2023 glDisable(GL_TEXTURE_2D);
2024#else
2025
2026#ifdef __WXQT__
2027 wxFont font = GetOCPNGUIScaledFont_PlugIn(_("Dialog"));
2028#else
2029 wxFont font(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
2030 wxFONTWEIGHT_NORMAL);
2031#endif
2032
2033 wxString label = getLabelString(value, settings);
2034
2035 m_oDC->SetFont(font);
2036 int w, h;
2037 m_oDC->GetTextExtent(label, &w, &h);
2038
2039 int label_offsetx = 5, label_offsety = 1;
2040 int x = p.x - label_offsetx, y = p.y - label_offsety;
2041 w += 2 * label_offsetx, h += 2 * label_offsety;
2042
2043 m_oDC->SetBrush(wxBrush(back_color));
2044 m_oDC->DrawRoundedRectangle(x, y, w, h, 0);
2045
2046 /* draw bounding rectangle */
2047 m_oDC->SetPen(wxPen(wxColour(0, 0, 0), 1));
2048 m_oDC->DrawLine(x, y, x + w, y);
2049 m_oDC->DrawLine(x + w, y, x + w, y + h);
2050 m_oDC->DrawLine(x + w, y + h, x, y + h);
2051 m_oDC->DrawLine(x, y + h, x, y);
2052
2053 m_oDC->DrawText(label, p.x, p.y);
2054
2055#endif
2056#endif
2057 }
2058}
2059
2060void GRIBOverlayFactory::RenderGribParticles(int settings, GribRecord **pGR,
2061 PlugIn_ViewPort *vp) {
2062 if (!m_Settings.Settings[settings].m_bParticles) return;
2063
2064 // need two records or a polar record to draw arrows
2065 GribRecord *pGRX, *pGRY;
2066 int idx, idy;
2067 bool polar;
2068 SettingsIdToGribId(settings, idx, idy, polar);
2069 if (idx < 0 || idy < 0) return;
2070
2071 pGRX = pGR[idx];
2072 pGRY = pGR[idy];
2073
2074 if (!pGRX || !pGRY) return;
2075
2076 wxStopWatch sw;
2077 sw.Start();
2078
2079 if (m_ParticleMap && m_ParticleMap->m_Setting != settings) ClearParticles();
2080
2081 if (!m_ParticleMap) m_ParticleMap = new ParticleMap(settings);
2082
2083 std::vector<Particle> &particles = m_ParticleMap->m_Particles;
2084
2085 const int max_duration = 50;
2086 const int run_count = 6;
2087
2088 double density = m_Settings.Settings[settings].m_dParticleDensity;
2089 // density = density * sqrt(vp.view_scale_ppm);
2090
2091 int history_size = 27 / sqrt(density);
2092 history_size = wxMin(history_size, MAX_PARTICLE_HISTORY);
2093
2094 std::vector<Particle>::iterator it;
2095 // if the history size changed
2096 if (m_ParticleMap->history_size != history_size) {
2097 for (unsigned int i = 0; i < particles.size(); i++) {
2098 Particle &it = particles[i];
2099 if (m_ParticleMap->history_size > history_size &&
2100 it.m_HistoryPos >= history_size) {
2101 it = particles[particles.size() - 1];
2102 particles.pop_back();
2103 i--;
2104 continue;
2105 }
2106
2107 it.m_HistorySize = it.m_HistoryPos + 1;
2108 }
2109 m_ParticleMap->history_size = history_size;
2110 }
2111
2112 // Did the viewport change? update cached screen coordinates
2113 // we could use normalized coordinates in opengl and avoid this
2114 PlugIn_ViewPort &lvp = m_ParticleMap->last_viewport;
2115 if (lvp.bValid == false || vp->view_scale_ppm != lvp.view_scale_ppm ||
2116 vp->skew != lvp.skew || vp->rotation != lvp.rotation) {
2117 for (it = particles.begin(); it != particles.end(); it++)
2118 for (int i = 0; i < it->m_HistorySize; i++) {
2119 Particle::ParticleNode &n = it->m_History[i];
2120 float(&p)[2] = n.m_Pos;
2121 if (p[0] == -10000) continue;
2122
2123 wxPoint ps;
2124 GetCanvasPixLL(vp, &ps, p[1], p[0]);
2125 n.m_Screen[0] = ps.x;
2126 n.m_Screen[1] = ps.y;
2127 }
2128
2129 lvp = *vp;
2130 } else // just panning, do quicker update
2131 if (vp->clat != lvp.clat || vp->clon != lvp.clon) {
2132 wxPoint p1, p2;
2133 GetCanvasPixLL(vp, &p1, 0, 0);
2134 GetCanvasPixLL(&lvp, &p2, 0, 0);
2135
2136 p1 -= p2;
2137
2138 for (it = particles.begin(); it != particles.end(); it++)
2139 for (int i = 0; i < it->m_HistorySize; i++) {
2140 Particle::ParticleNode &n = it->m_History[i];
2141 float(&p)[2] = n.m_Pos;
2142 if (p[0] == -10000) continue;
2143
2144 n.m_Screen[0] += p1.x;
2145 n.m_Screen[1] += p1.y;
2146 }
2147 lvp = *vp;
2148 }
2149
2150 double ptime = 0;
2151
2152 // update particle map
2153 if (m_bUpdateParticles) {
2154 for (unsigned int i = 0; i < particles.size(); i++) {
2155 Particle &it = particles[i];
2156
2157 // Update the interpolation factor
2158 if (++it.m_Run < run_count) continue;
2159 it.m_Run = 0;
2160
2161 // don't allow particle to live too long
2162 if (it.m_Duration > max_duration) {
2163 it = particles[particles.size() - 1];
2164 particles.pop_back();
2165 i--;
2166 continue;
2167 }
2168
2169 it.m_Duration++;
2170
2171 float(&pp)[2] = it.m_History[it.m_HistoryPos].m_Pos;
2172
2173 // maximum history size
2174 if (++it.m_HistorySize > history_size) it.m_HistorySize = history_size;
2175
2176 if (++it.m_HistoryPos >= history_size) it.m_HistoryPos = 0;
2177
2178 Particle::ParticleNode &n = it.m_History[it.m_HistoryPos];
2179 float(&p)[2] = n.m_Pos;
2180 double vkn = 0, ang;
2181
2182 if (it.m_Duration < max_duration - history_size &&
2183 GribRecord::getInterpolatedValues(vkn, ang, pGRX, pGRY, pp[0],
2184 pp[1]) &&
2185 vkn > 0 && vkn < 100) {
2186 vkn = m_Settings.CalibrateValue(settings, vkn);
2187 double d;
2188 if (settings == GribOverlaySettings::CURRENT)
2189 d = vkn * run_count;
2190 else
2191 d = vkn * run_count / 4;
2192
2193 ang += 180;
2194
2195#if 0 // elliptical very accurate but incredibly slow
2196 double dp[2];
2198 d, &dp[1], &dp[0]);
2199 p[0] = dp[0];
2200 p[1] = dp[1];
2201#elif 0 // really fast rectangular.. not really good at high latitudes
2202
2203 float angr = ang / 180 * M_PI;
2204 p[0] = pp[0] + sinf(angr) * d / 60;
2205 p[1] = pp[1] + cosf(angr) * d / 60;
2206#else // spherical (close enough)
2207 float angr = ang / 180 * M_PI;
2208 float latr = pp[1] * M_PI / 180;
2209 float D = d / 3443; // earth radius in nm
2210 float sD = sinf(D), cD = cosf(D);
2211 float sy = sinf(latr), cy = cosf(latr);
2212 float sa = sinf(angr), ca = cosf(angr);
2213
2214 p[0] = pp[0] + asinf(sa * sD / cy) * 180 / M_PI;
2215 p[1] = asinf(sy * cD + cy * sD * ca) * 180 / M_PI;
2216#endif
2217 wxPoint ps;
2218 GetCanvasPixLL(vp, &ps, p[1], p[0]);
2219
2220 n.m_Screen[0] = ps.x;
2221 n.m_Screen[1] = ps.y;
2222
2223 wxColor c = GetGraphicColor(settings, vkn);
2224
2225 n.m_Color[0] = c.Red();
2226 n.m_Color[1] = c.Green();
2227 n.m_Color[2] = c.Blue();
2228 } else
2229 p[0] = -10000;
2230 ptime += sw.Time();
2231 }
2232 }
2233 m_bUpdateParticles = false;
2234
2235 int total_particles = density * pGRX->getNi() * pGRX->getNj();
2236
2237 // set max cap to avoid locking the program up
2238 if (total_particles > 60000) total_particles = 60000;
2239
2240 // remove particles if needed;
2241 int remove_particles = ((int)particles.size() - total_particles) / 16;
2242 for (int i = 0; i < remove_particles; i++) particles.pop_back();
2243
2244 // add new particles as needed
2245 int run = 0;
2246 int new_particles = (total_particles - (int)particles.size()) / 64;
2247
2248 for (int npi = 0; npi < new_particles; npi++) {
2249 float p[2];
2250 double vkn, ang;
2251 for (int i = 0; i < 20; i++) {
2252 // random position in the grib area
2253 p[0] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) *
2254 (pGRX->getLonMax() - pGRX->getLonMin()) +
2255 pGRX->getLonMin();
2256 p[1] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) *
2257 (pGRX->getLatMax() - pGRX->getLatMin()) +
2258 pGRX->getLatMin();
2259
2260 if (GribRecord::getInterpolatedValues(vkn, ang, pGRX, pGRY, p[0], p[1]) &&
2261 vkn > 0 && vkn < 100)
2262 vkn = m_Settings.CalibrateValue(settings, vkn);
2263 else
2264 continue; // try again
2265
2266 /* try hard to find a random position where current is faster than 1 knot
2267 */
2268 if (settings != GribOverlaySettings::CURRENT || vkn > 1 - (double)i / 20)
2269 break;
2270 }
2271
2272 Particle np;
2273 np.m_Duration = rand() % (max_duration / 2);
2274 np.m_HistoryPos = 0;
2275 np.m_HistorySize = 1;
2276 np.m_Run = run++;
2277 if (run == run_count) run = 0;
2278
2279 memcpy(np.m_History[np.m_HistoryPos].m_Pos, p, sizeof p);
2280
2281 wxPoint ps;
2282 GetCanvasPixLL(vp, &ps, p[1], p[0]);
2283 np.m_History[np.m_HistoryPos].m_Screen[0] = ps.x;
2284 np.m_History[np.m_HistoryPos].m_Screen[1] = ps.y;
2285
2286 wxColour c = GetGraphicColor(settings, vkn);
2287 np.m_History[np.m_HistoryPos].m_Color[0] = c.Red();
2288 np.m_History[np.m_HistoryPos].m_Color[1] = c.Green();
2289 np.m_History[np.m_HistoryPos].m_Color[2] = c.Blue();
2290
2291 particles.push_back(np);
2292 }
2293
2294 // settings for opengl lines
2295 if (!m_pdc) {
2296 // Enable anti-aliased lines, at best quality
2297 glEnable(GL_LINE_SMOOTH);
2298 glEnable(GL_BLEND);
2299 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2300 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
2301 glLineWidth(2.3f);
2302 }
2303
2304 int cnt = 0;
2305 unsigned char *&ca = m_ParticleMap->color_array;
2306 float *&va = m_ParticleMap->vertex_array;
2307 float *&caf = m_ParticleMap->color_float_array;
2308
2309 if (m_ParticleMap->array_size < particles.size() && !m_pdc) {
2310 m_ParticleMap->array_size = 2 * particles.size();
2311 delete[] ca;
2312 delete[] va;
2313 delete[] caf;
2314
2315 ca =
2316 new unsigned char[m_ParticleMap->array_size * MAX_PARTICLE_HISTORY * 8];
2317 caf = new float[m_ParticleMap->array_size * MAX_PARTICLE_HISTORY * 8];
2318 va = new float[m_ParticleMap->array_size * MAX_PARTICLE_HISTORY * 4];
2319 }
2320
2321 // draw particles
2322 for (std::vector<Particle>::iterator it = particles.begin();
2323 it != particles.end(); it++) {
2324 wxUint8 alpha = 250;
2325
2326 int i = it->m_HistoryPos;
2327
2328 bool lip_valid = false;
2329 float *lp = nullptr, lip[2];
2330 wxUint8 lc[4];
2331 float lcf[4];
2332
2333 for (;;) {
2334 float(&dp)[2] = it->m_History[i].m_Pos;
2335 if (dp[0] != -10000) {
2336 float(&sp)[2] = it->m_History[i].m_Screen;
2337 wxUint8(&ci)[3] = it->m_History[i].m_Color;
2338
2339 wxUint8 c[4] = {ci[0], ci[1], (unsigned char)(ci[2] + 240 - alpha / 2),
2340 alpha};
2341 float cf[4];
2342 cf[0] = ci[0] / 256.;
2343 cf[1] = ci[1] / 256.;
2344 cf[2] = ((unsigned char)(ci[2] + 240 - alpha / 2)) / 256.;
2345 cf[3] = alpha / 256.;
2346
2347 if (lp && fabsf(lp[0] - sp[0]) < vp->pix_width) {
2348 float sip[2];
2349
2350 // interpolate between points.. a cubic interpolation
2351 // might allow a much higher run_count
2352 float d = (float)it->m_Run / run_count;
2353 for (int j = 0; j < 2; j++) sip[j] = d * lp[j] + (1 - d) * sp[j];
2354
2355 if (lip_valid && fabsf(lip[0] - sip[0]) < vp->pix_width) {
2356 if (m_pdc) {
2357 m_pdc->SetPen(wxPen(wxColour(c[0], c[1], c[2]), 2));
2358 m_pdc->DrawLine(sip[0], sip[1], lip[0], lip[1]);
2359 } else {
2360 memcpy(ca + 4 * cnt, c, sizeof lc);
2361 memcpy(caf + 4 * cnt, cf, sizeof lcf);
2362 memcpy(va + 2 * cnt, lip, sizeof sp);
2363 cnt++;
2364 memcpy(ca + 4 * cnt, lc, sizeof c);
2365 memcpy(caf + 4 * cnt, lcf, sizeof cf);
2366 memcpy(va + 2 * cnt, sip, sizeof sp);
2367 cnt++;
2368 }
2369 }
2370
2371 memcpy(lip, sip, sizeof lip);
2372 lip_valid = true;
2373 }
2374
2375 memcpy(lc, c, sizeof lc);
2376 memcpy(lcf, cf, sizeof lcf);
2377
2378 lp = sp;
2379 }
2380
2381 if (--i < 0) {
2382 i = history_size - 1;
2383 if (i >= it->m_HistorySize) break;
2384 }
2385
2386 if (i == it->m_HistoryPos) break;
2387
2388 alpha -= 240 / history_size;
2389 }
2390 }
2391
2392 if (!m_pdc) {
2393 if (m_oDC) {
2394 m_oDC->DrawGLLineArray(cnt, va, caf, ca, false);
2395 }
2396 }
2397
2398 // On some platforms, especially slow ones, the GPU will lag behind the CPU.
2399 // This affects the UI in strange ways.
2400 // So, force the GPU to flush all of its outstanding commands on the outer
2401 // loop This will have no real affect on most machines.
2402#ifdef __WXMSW__
2403 if (!m_pdc) glFlush();
2404#endif
2405
2406 int time = sw.Time();
2407
2408 // Try to run at 20 fps,
2409 // But also arrange not to consume more than 33% CPU(core) duty cycle
2410 m_tParticleTimer.Start(wxMax(50 - time, 2 * time), wxTIMER_ONE_SHOT);
2411
2412#if 0
2413 static int total_time;
2414 total_time += time;
2415 static int total_count;
2416 if(++total_count == 100) {
2417 printf("time: %.2f\n", (double)total_time / total_count);
2418 total_time = total_count = 0;
2419 }
2420#endif
2421}
2422
2423void GRIBOverlayFactory::OnParticleTimer(wxTimerEvent &event) {
2424 m_bUpdateParticles = true;
2425
2426 // If multicanvas are active, render the overlay on the right canvas only
2427 if (GetCanvasCount() > 1) // multi?
2428 GetCanvasByIndex(1)->Refresh(false); // update the last rendered canvas
2429 else
2430 GetOCPNCanvasWindow()->Refresh(false);
2431}
2432
2433void GRIBOverlayFactory::DrawProjectedPosition(int x, int y) {
2434 if (m_pdc) {
2435 wxDC &dc = *m_pdc;
2436 dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
2437 dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
2438 dc.DrawRectangle(x, y, 20, 20);
2439 dc.DrawLine(x, y, x + 20, y + 20);
2440 dc.DrawLine(x, y + 20, x + 20, y);
2441 } else {
2442 if (m_oDC) {
2443 m_oDC->SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
2444 m_oDC->SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
2445 m_oDC->DrawRectangle(x - 10, y - 10, 20, 20);
2446 m_oDC->StrokeLine(x - 10, y - 10, x + 10, y + 10);
2447 m_oDC->StrokeLine(x - 10, y + 10, x + 10, y - 10);
2448 }
2449 }
2450}
2451
2452void GRIBOverlayFactory::DrawMessageWindow(wxString msg, int x, int y,
2453 wxFont *mfont) {
2454 if (msg.empty()) return;
2455
2456 int ScaleBare_H = 30; // futur : get the position/size from API?
2457
2458 if (m_pdc) {
2459 wxDC &dc = *m_pdc;
2460 dc.SetFont(*mfont);
2461 dc.SetPen(*wxTRANSPARENT_PEN);
2462
2463 dc.SetBrush(wxColour(243, 229, 47));
2464 int w, h;
2465 dc.GetMultiLineTextExtent(msg, &w, &h);
2466 h += 2;
2467 int yp = y - (ScaleBare_H + GetChartbarHeight() + h);
2468
2469 int label_offset = 10;
2470 int wdraw = w + (label_offset * 2);
2471 dc.DrawRectangle(0, yp, wdraw, h);
2472 dc.DrawLabel(msg, wxRect(label_offset, yp, wdraw, h),
2473 wxALIGN_LEFT | wxALIGN_CENTRE_VERTICAL);
2474 } else {
2475 if (m_oDC) {
2476 m_oDC->SetFont(*mfont);
2477 m_oDC->SetPen(*wxTRANSPARENT_PEN);
2478
2479 m_oDC->SetBrush(wxColour(243, 229, 47));
2480 int w, h;
2481 m_oDC->GetTextExtent(msg, &w, &h);
2482 h += 2;
2483
2484 int label_offset = 10;
2485 int wdraw = w + (label_offset * 2);
2486 wdraw *= g_ContentScaleFactor;
2487 h *= g_ContentScaleFactor;
2488 int yp = y - (ScaleBare_H + GetChartbarHeight() + h);
2489
2490 m_oDC->DrawRectangle(0, yp, wdraw, h);
2491 m_oDC->DrawText(msg, label_offset, yp);
2492 }
2493 /*
2494 m_TexFontMessage.Build(*mfont);
2495 int w, h;
2496 m_TexFontMessage.GetTextExtent( msg, &w, &h);
2497 h += 2;
2498 int yp = y - ( 2 * GetChartbarHeight() + h );
2499
2500 glColor3ub( 243, 229, 47 );
2501
2502 glBegin(GL_QUADS);
2503 glVertex2i(0, yp);
2504 glVertex2i(w, yp);
2505 glVertex2i(w, yp+h);
2506 glVertex2i(0, yp+h);
2507 glEnd();
2508
2509 glEnable(GL_BLEND);
2510 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
2511
2512 glColor3ub( 0, 0, 0 );
2513 glEnable(GL_TEXTURE_2D);
2514 m_TexFontMessage.RenderString( msg, 0, yp);
2515 glDisable(GL_TEXTURE_2D);
2516 */
2517 }
2518}
2519
2520void GRIBOverlayFactory::drawDoubleArrow(int x, int y, double ang,
2521 wxColour arrowColor, int arrowWidth,
2522 int arrowSizeIdx, double scale) {
2523 if (m_pdc) {
2524 wxPen pen(arrowColor, 2);
2525 m_pdc->SetPen(pen);
2526 m_pdc->SetBrush(*wxTRANSPARENT_BRUSH);
2527#if wxUSE_GRAPHICS_CONTEXT
2528 if (m_hiDefGraphics && m_gdc) m_gdc->SetPen(pen);
2529#endif
2530 } else {
2531 if (m_oDC) {
2532 wxPen pen(arrowColor, arrowWidth);
2533 m_oDC->SetPen(pen);
2534 }
2535 }
2536
2537 drawLineBuffer(m_DoubleArrow[arrowSizeIdx], x, y, ang, scale);
2538}
2539
2540void GRIBOverlayFactory::drawSingleArrow(int x, int y, double ang,
2541 wxColour arrowColor, int arrowWidth,
2542 int arrowSizeIdx, double scale) {
2543 if (m_pdc) {
2544 wxPen pen(arrowColor, arrowWidth);
2545 m_pdc->SetPen(pen);
2546 m_pdc->SetBrush(*wxTRANSPARENT_BRUSH);
2547#if wxUSE_GRAPHICS_CONTEXT
2548 if (m_hiDefGraphics && m_gdc) m_gdc->SetPen(pen);
2549#endif
2550 } else {
2551 if (m_oDC) {
2552 wxPen pen(arrowColor, arrowWidth);
2553 m_oDC->SetPen(pen);
2554 }
2555 }
2556
2557 drawLineBuffer(m_SingleArrow[arrowSizeIdx], x, y, ang, scale);
2558}
2559
2560void GRIBOverlayFactory::drawWindArrowWithBarbs(int settings, int x, int y,
2561 double vkn, double ang,
2562 bool south, wxColour arrowColor,
2563 double rotate_angle) {
2564 if (m_Settings.Settings[settings].m_iBarbedColour == 1)
2565 arrowColor = GetGraphicColor(settings, vkn);
2566
2567// TODO
2568// Needs investigation
2569// This conditional should not really be necessary, but is safe.
2570#ifndef __MSVC__
2571 float penWidth = .6 / m_pixelMM;
2572#else
2573 float penWidth = .4 / m_pixelMM;
2574#endif
2575 penWidth = wxMin(penWidth, 3.0);
2576
2577 if (m_pdc) {
2578 wxPen pen(arrowColor, 2);
2579 m_pdc->SetPen(pen);
2580 m_pdc->SetBrush(*wxTRANSPARENT_BRUSH);
2581
2582#if wxUSE_GRAPHICS_CONTEXT
2583 if (m_hiDefGraphics && m_gdc) m_gdc->SetPen(pen);
2584#endif
2585 }
2586#ifdef ocpnUSE_GL
2587 else {
2588 if (m_oDC) {
2589 wxPen pen(arrowColor, penWidth);
2590 m_oDC->SetPen(pen);
2591 }
2592 // else
2593 // glColor3ub(arrowColor.Red(), arrowColor.Green(),
2594 // arrowColor.Blue());
2595 }
2596#endif
2597
2598 int cacheidx;
2599
2600 if (vkn < 1)
2601 cacheidx = 0;
2602 else if (vkn < 2.5)
2603 cacheidx = 1;
2604 else if (vkn < 40)
2605 cacheidx = (int)(vkn + 2.5) / 5;
2606 else if (vkn < 90)
2607 cacheidx = (int)(vkn + 5) / 10 + 4;
2608 else
2609 cacheidx = 13;
2610
2611 ang += rotate_angle;
2612
2613 drawLineBuffer(m_WindArrowCache[cacheidx], x, y, ang, 1.0, south,
2614 m_bDrawBarbedArrowHead);
2615}
2616
2617void GRIBOverlayFactory::drawLineBuffer(LineBuffer &buffer, int x, int y,
2618 double ang, double scale, bool south,
2619 bool head) {
2620 // transform vertexes by angle
2621 float six = sinf(ang), cox = cosf(ang), siy, coy;
2622 if (south)
2623 siy = -six, coy = -cox;
2624 else
2625 siy = six, coy = cox;
2626
2627 float vertexes[40];
2628 int count = buffer.count;
2629
2630 if (!head) {
2631 count -= 2;
2632 }
2633 wxASSERT(sizeof vertexes / sizeof *vertexes >= (unsigned)count * 4);
2634 for (int i = 0; i < 2 * count; i++) {
2635 int j = i;
2636 if (!head && i > 1) j += 4;
2637 float *k = buffer.lines + 2 * j;
2638 vertexes[2 * i + 0] = k[0] * cox * scale + k[1] * siy * scale + x;
2639 vertexes[2 * i + 1] = k[0] * six * scale - k[1] * coy * scale + y;
2640 }
2641
2642 if (m_pdc) {
2643 for (int i = 0; i < count; i++) {
2644 float *l = vertexes + 4 * i;
2645#if wxUSE_GRAPHICS_CONTEXT
2646 if (m_hiDefGraphics && m_gdc)
2647 m_gdc->StrokeLine(l[0], l[1], l[2], l[3]);
2648 else
2649#endif
2650 m_pdc->DrawLine(l[0], l[1], l[2], l[3]);
2651 }
2652 } else { // OpenGL mode
2653#ifdef ocpnUSE_GL
2654 if (m_oDC) {
2655 for (int i = 0; i < count; i++) {
2656 float *l = vertexes + 4 * i;
2657 if (m_hiDefGraphics)
2658 m_oDC->StrokeLine(l[0], l[1], l[2], l[3]);
2659 else
2660 m_oDC->DrawLine(l[0], l[1], l[2], l[3]);
2661 }
2662 }
2663
2664// glVertexPointer(2, GL_FLOAT, 2*sizeof(float), vertexes);
2665// glDrawArrays(GL_LINES, 0, 2*count);
2666#endif
2667 }
2668}
2669
2670#ifdef ocpnUSE_GL
2671// Render a texture
2672// x/y : origin in screen pixels of UPPER RIGHT corner of render rectangle
2673// width/height : in screen pixels
2674void GRIBOverlayFactory::DrawSingleGLTexture(GribOverlay *pGO, GribRecord *pGR,
2675 double uv[], double x, double y,
2676 double width, double height) {
2677#if 1 // def __OCPN__ANDROID__
2678
2679 glEnable(texture_format);
2680
2681 glEnable(GL_BLEND);
2682 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2683
2684 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
2685
2686 float coords[8];
2687
2688 coords[0] = -width;
2689 coords[1] = -height;
2690 coords[2] = 0;
2691 coords[3] = -height;
2692 coords[4] = 0;
2693 coords[5] = 0;
2694 coords[6] = -width;
2695 coords[7] = 0;
2696
2697 extern int pi_texture_2D_shader_program;
2698 glUseProgram(pi_texture_2D_shader_program);
2699
2700 // Get pointers to the attributes in the program.
2701 GLint mPosAttrib = glGetAttribLocation(pi_texture_2D_shader_program, "aPos");
2702 GLint mUvAttrib = glGetAttribLocation(pi_texture_2D_shader_program, "aUV");
2703
2704 // Set up the texture sampler to texture unit 0
2705 GLint texUni = glGetUniformLocation(pi_texture_2D_shader_program, "uTex");
2706 glUniform1i(texUni, 0);
2707
2708 // Disable VBO's (vertex buffer objects) for attributes.
2709 glBindBuffer(GL_ARRAY_BUFFER, 0);
2710 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
2711
2712 // Set the attribute mPosAttrib with the vertices in the screen coordinates...
2713 glVertexAttribPointer(mPosAttrib, 2, GL_FLOAT, GL_FALSE, 0, coords);
2714 // ... and enable it.
2715 glEnableVertexAttribArray(mPosAttrib);
2716
2717 // Set the attribute mUvAttrib with the vertices in the GL coordinates...
2718 glVertexAttribPointer(mUvAttrib, 2, GL_FLOAT, GL_FALSE, 0, uv);
2719 // ... and enable it.
2720 glEnableVertexAttribArray(mUvAttrib);
2721
2722 // Rotate
2723 float angle = 0;
2724 mat4x4 I, Q;
2725 mat4x4_identity(I);
2726 mat4x4_rotate_Z(Q, I, angle);
2727
2728 // Translate
2729 Q[3][0] = x;
2730 Q[3][1] = y;
2731
2732 GLint matloc =
2733 glGetUniformLocation(pi_texture_2D_shader_program, "TransformMatrix");
2734 glUniformMatrix4fv(matloc, 1, GL_FALSE, (const GLfloat *)Q);
2735
2736 // Select the active texture unit.
2737 glActiveTexture(GL_TEXTURE0);
2738
2739// Perform the actual drawing.
2740
2741// For some reason, glDrawElements is busted on Android
2742// So we do this a hard ugly way, drawing two triangles...
2743#if 0
2744 GLushort indices1[] = {0,1,3,2};
2745 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices1);
2746#else
2747
2748 float co1[8];
2749 co1[0] = coords[0];
2750 co1[1] = coords[1];
2751 co1[2] = coords[2];
2752 co1[3] = coords[3];
2753 co1[4] = coords[6];
2754 co1[5] = coords[7];
2755 co1[6] = coords[4];
2756 co1[7] = coords[5];
2757
2758 float tco1[8];
2759 tco1[0] = uv[0];
2760 tco1[1] = uv[1];
2761 tco1[2] = uv[2];
2762 tco1[3] = uv[3];
2763 tco1[4] = uv[6];
2764 tco1[5] = uv[7];
2765 tco1[6] = uv[4];
2766 tco1[7] = uv[5];
2767
2768 glVertexAttribPointer(mPosAttrib, 2, GL_FLOAT, GL_FALSE, 0, co1);
2769 glVertexAttribPointer(mUvAttrib, 2, GL_FLOAT, GL_FALSE, 0, tco1);
2770
2771 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
2772
2773 glDisable(GL_BLEND);
2774 glDisable(texture_format);
2775
2776 // Restore identity matrix
2777 mat4x4_identity(I);
2778 glUniformMatrix4fv(matloc, 1, GL_FALSE, (const GLfloat *)I);
2779
2780#endif
2781
2782#else
2783
2784 glColor4f(1, 1, 1, 1);
2785 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
2786
2787 if (texture_format != GL_TEXTURE_2D) {
2788 for (int i = 0; i < 4; i++) {
2789 uv[i * 2] *= pGR->getNi();
2790 uv[(i * 2) + 1] *= pGR->getNj();
2791 }
2792 }
2793
2794 glBegin(GL_QUADS);
2795 glTexCoord2d(uv[0], uv[1]), glVertex2f(x - width, y - height);
2796 glTexCoord2d(uv[2], uv[3]), glVertex2f(x, y - height);
2797 glTexCoord2d(uv[4], uv[5]), glVertex2f(x, y);
2798 glTexCoord2d(uv[6], uv[7]), glVertex2f(x - width, y);
2799 glEnd();
2800
2801#endif
2802}
2803
2804void GRIBOverlayFactory::DrawGLTexture(GribOverlay *pGO, GribRecord *pGR,
2805 PlugIn_ViewPort *vp) {
2806 glEnable(texture_format);
2807 glBindTexture(texture_format, pGO->m_iTexture);
2808
2809 glEnable(GL_BLEND);
2810 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2811
2812 double lat_min = pGR->getLatMin(), lon_min = pGR->getLonMin();
2813
2814 bool repeat = pGR->getLonMin() == 0 && pGR->getLonMax() + pGR->getDi() == 360;
2815
2816 // how to break screen up, because projections may not be linear
2817 // smaller values offer more precision but become irrelevant
2818 // at lower zoom levels and near poles, use smaller tiles
2819
2820 // This formula is generally "good enough" but is not optimal,
2821 // certainly not for all projections, and may result in
2822 // more tiles than actually needed in some cases
2823
2824 double pw = vp->view_scale_ppm * 1e6 / (pow(2, fabs(vp->clat) / 25));
2825 if (pw < 20) // minimum 20 pixel to avoid too many tiles
2826 pw = 20;
2827
2828 int xsquares = ceil(vp->pix_width / pw), ysquares = ceil(vp->pix_height / pw);
2829
2830 // optimization for non-rotated mercator, since longitude is linear
2831 if (vp->rotation == 0 && vp->m_projection_type == PI_PROJECTION_MERCATOR)
2832 xsquares = 1;
2833
2834 // It is possible to have only 1 square when the viewport covers more than
2835 // 180 longitudes but there is more logic needed. This is simpler.
2836 // if(vp->lon_max - vp->lon_min >= 180) {
2837 xsquares = wxMax(xsquares, 2);
2838 ysquares = wxMax(ysquares, 2);
2839 // }
2840
2841 double xs = vp->pix_width / double(xsquares),
2842 ys = vp->pix_height / double(ysquares);
2843 int i = 0, j = 0;
2844 typedef double mx[2][2];
2845
2846 mx *lva = new mx[xsquares + 1];
2847 int tw = pGO->m_iTextureDim[0], th = pGO->m_iTextureDim[1];
2848 double latstep = fabs(pGR->getDj()) / (th - 2 - 1) * (pGR->getNj() - 1);
2849 double lonstep = pGR->getDi() / (tw - 2 * !repeat - 1) * (pGR->getNi() - 1);
2850
2851 double potNormX = (double)pGO->m_iTexDataDim[0] / tw;
2852 double potNormY = (double)pGO->m_iTexDataDim[1] / th;
2853
2854 double clon = (lon_min + pGR->getLonMax()) / 2;
2855
2856 for (double y = 0; y < vp->pix_height + ys / 2; y += ys) {
2857 i = 0;
2858
2859 for (double x = 0; x < vp->pix_width + xs / 2; x += xs) {
2860 double lat, lon;
2861 wxPoint p(x, y);
2862 GetCanvasLLPix(vp, p, &lat, &lon);
2863
2864 if (!repeat) {
2865 if (clon - lon > 180)
2866 lon += 360;
2867 else if (lon - clon > 180)
2868 lon -= 360;
2869 }
2870
2871 lva[i][j][0] =
2872 (((lon - lon_min) / lonstep - repeat + 1.5) / tw) * potNormX;
2873 lva[i][j][1] = (((lat - lat_min) / latstep + 1.5) / th) * potNormY;
2874
2875 if (pGR->getDj() < 0) lva[i][j][1] = 1 - lva[i][j][1];
2876
2877 if (x > 0 && y > 0) {
2878 double u0 = lva[i - 1][!j][0], v0 = lva[i - 1][!j][1];
2879 double u1 = lva[i][!j][0], v1 = lva[i][!j][1];
2880 double u2 = lva[i][j][0], v2 = lva[i][j][1];
2881 double u3 = lva[i - 1][j][0], v3 = lva[i - 1][j][1];
2882
2883 if (repeat) { /* ensure all 4 texcoords are in the same phase */
2884 if (u1 - u0 > .5)
2885 u1--;
2886 else if (u0 - u1 > .5)
2887 u1++;
2888 if (u2 - u0 > .5)
2889 u2--;
2890 else if (u0 - u2 > .5)
2891 u2++;
2892 if (u3 - u0 > .5)
2893 u3--;
2894 else if (u0 - u3 > .5)
2895 u3++;
2896 }
2897
2898 if ((repeat ||
2899 ((u0 >= 0 || u1 >= 0 || u2 >= 0 || u3 >= 0) && // optimzations
2900 (u0 <= 1 || u1 <= 1 || u2 <= 1 || u3 <= 1))) &&
2901 (v0 >= 0 || v1 >= 0 || v2 >= 0 || v3 >= 0) &&
2902 (v0 <= 1 || v1 <= 1 || v2 <= 1 || v3 <= 1)) {
2903 double uv[8];
2904 uv[0] = u0;
2905 uv[1] = v0;
2906 uv[2] = u1;
2907 uv[3] = v1;
2908 uv[4] = u2;
2909 uv[5] = v2;
2910 uv[6] = u3;
2911 uv[7] = v3;
2912
2913 if (u1 > u0) {
2914 DrawSingleGLTexture(pGO, pGR, uv, x, y, xs, ys);
2915 }
2916 }
2917 }
2918
2919 i++;
2920 }
2921 j = !j;
2922 }
2923 delete[] lva;
2924
2925 glDisable(GL_BLEND);
2926 glDisable(texture_format);
2927}
2928#endif
GRIB Data Visualization and Rendering Factory.
@ Idx_COMP_REFL
Composite radar reflectivity in dBZ (decibel relative to Z)
@ Idx_PRECIP_TOT
Precipitation data in millimeters per hour.
@ Idx_AIR_TEMP
Air temperature at 2m in Kelvin (K)
@ Idx_PRESSURE
Surface pressure in Pascal (Pa)
@ Idx_WVDIR
Wave direction.
@ Idx_CLOUD_TOT
Total cloud cover in % (percent, range 0-100%)
@ Idx_WIND_GUST
Wind gust speed at surface in m/s.
@ Idx_WIND_VX
Surface wind velocity X component in m/s.
@ Idx_HTSIGW
Significant wave height in meters.
@ Idx_SEACURRENT_VY
Sea current velocity Y component in m/s.
@ Idx_SEA_TEMP
Sea surface temperature in Kelvin (K)
@ Idx_WIND_VY
Surface wind velocity Y component in m/s.
@ Idx_SEACURRENT_VX
Sea current velocity X component in m/s.
@ Idx_CAPE
Convective Available Potential Energy in J/kg (Joules per kilogram)
GRIB Weather Data Control Interface.
void GetProjectedLatLon(int &x, int &y, PlugIn_ViewPort *vp)
Gets the projected position of vessel based on current course, speed and forecast time.
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.
Contains view parameters and status information for a chart display viewport.
double view_scale_ppm
Display scale in pixels per meter.
int pix_width
Viewport width in pixels.
double lon_max
Maximum longitude of the viewport.
double clon
Center longitude of the viewport in decimal degrees.
double lat_max
Maximum latitude of the viewport.
int pix_height
Viewport height in pixels.
double clat
Center latitude of the viewport in decimal degrees.
double skew
Display skew angle in radians.
double rotation
Display rotation angle in radians.
bool bValid
True if this viewport is valid and can be used for rendering.
double lon_min
Minimum longitude of the viewport.
double lat_min
Minimum latitude of the viewport.
int m_projection_type
Chart projection type (PROJECTION_MERCATOR, etc.)
@ PI_PROJECTION_MERCATOR
Mercator projection, standard for navigation charts.
wxWindow * GetOCPNCanvasWindow()
Gets OpenCPN's main canvas window.
int GetCanvasCount()
Gets total number of chart canvases.
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.
void PositionBearingDistanceMercator_Plugin(double lat, double lon, double brg, double dist, double *dlat, double *dlon)
Calculates destination point given starting point, bearing and distance.
double PlugInGetDisplaySizeMM()
Gets physical display size in millimeters.
void GetCanvasPixLL(PlugIn_ViewPort *vp, wxPoint *pp, double lat, double lon)
Converts lat/lon to canvas physical pixel coordinates.
int GetChartbarHeight()
Gets height of chart bar in pixels.
wxWindow * GetCanvasByIndex(int canvasIndex)
Gets chart canvas window by index.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
void GetCanvasLLPix(PlugIn_ViewPort *vp, wxPoint p, double *plat, double *plon)
Converts canvas physical pixel coordinates to lat/lon.
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.