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