OpenCPN Partial API docs
Loading...
Searching...
No Matches
grib_ui_dlg.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2010 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 "pi_gl.h" // Must included before anything using GL stuff
25
26#include "wx/wx.h"
27#include "wx/tokenzr.h"
28#include "wx/datetime.h"
29#include "wx/sound.h"
30#include <wx/wfstream.h>
31#include <wx/dir.h>
32#include <wx/filename.h>
33#include <wx/debug.h>
34#include <wx/graphics.h>
35#include <wx/regex.h>
36#include <wx/sysopt.h>
37#include <wx/stdpaths.h>
38
39#include <stdlib.h>
40#include <math.h>
41#include <time.h>
42
43#include "jsonwriter.h"
44#include "ocpn_plugin.h"
45#include "grib_pi.h"
46#include "grib_table.h"
47#include "email.h"
48#include "folder.xpm"
49#include "grib_ui_dlg.h"
50#include <wx/arrimpl.cpp>
51
52#ifdef __ANDROID__
53#include "android_jvm.h"
54#endif
55
56// general variables
57double m_cursor_lat, m_cursor_lon;
58int m_Altitude;
59int m_DialogStyle;
85
86extern GribPi *g_pi;
87
88#ifdef __MSVC__
89#if _MSC_VER < 1700
90int round(double x) {
91 int i = (int)x;
92 if (x >= 0.0) {
93 return ((x - i) >= 0.5) ? (i + 1) : (i);
94 } else {
95 return (-x + i >= 0.5) ? (i - 1) : (i);
96 }
97}
98#endif
99#endif
100
101#ifdef __WXQT__
102#include "qdebug.h"
103#endif
104
105#if wxCHECK_VERSION(2, 9, 4) /* to work with wx 2.8 */
106#define SetBitmapLabelLabel SetBitmap
107#endif
108
109#define DEFAULT_STYLE \
110 = wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL
111
112WX_DEFINE_OBJARRAY(ArrayOfGribRecordSets);
113
114// Sort compare function for File Modification Time
115static int CompareFileStringTime(const wxString &first,
116 const wxString &second) {
117 wxFileName f(first);
118 wxFileName s(second);
119 wxTimeSpan sp = s.GetModificationTime() - f.GetModificationTime();
120 return sp.GetMinutes();
121
122 // return ::wxFileModificationTime(first) -
123 // ::wxFileModificationTime(second);
124}
125
126wxWindow *GetGRIBCanvas() {
127 wxWindow *wx;
128 // If multicanvas are active, render the overlay on the right canvas only
129 if (GetCanvasCount() > 1) // multi?
130 wx = GetCanvasByIndex(1);
131 else
132 wx = GetOCPNCanvasWindow();
133 return wx;
134}
135
136//---------------------------------------------------------------------------------------
137// GRIB Control Implementation
138//---------------------------------------------------------------------------------------
139/* interpolating constructor
140 as a possible optimization, write this function to also
141 take latitude longitude boundaries so the resulting record can be
142 a subset of the input, but also would need to be recomputed when panning the
143 screen */
145 : GribRecordSet(cnt) {
146 for (int i = 0; i < Idx_COUNT; i++) m_IsobarArray[i] = nullptr;
147}
148
149GribTimelineRecordSet::~GribTimelineRecordSet() {
150 // RemoveGribRecords();
151 ClearCachedData();
152}
153
154void GribTimelineRecordSet::ClearCachedData() {
155 for (int i = 0; i < Idx_COUNT; i++) {
156 if (m_IsobarArray[i]) {
157 // Clear out the cached isobars
158 for (unsigned int j = 0; j < m_IsobarArray[i]->GetCount(); j++) {
159 IsoLine *piso = (IsoLine *)m_IsobarArray[i]->Item(j);
160 delete piso;
161 }
162 delete m_IsobarArray[i];
163 m_IsobarArray[i] = nullptr;
164 }
165 }
166}
167
168//---------------------------------------------------------------------------------------
169// GRIB CtrlBar Implementation
170//---------------------------------------------------------------------------------------
171
172GRIBUICtrlBar::GRIBUICtrlBar(wxWindow *parent, wxWindowID id,
173 const wxString &title, const wxPoint &pos,
174 const wxSize &size, long style, GribPi *ppi,
175 double scale_factor)
176 : GRIBUICtrlBarBase(parent, id, title, pos, size, style, scale_factor) {
177 pParent = parent;
178 pPlugIn = ppi;
179 // Preinitialize the vierwport with an existing value, see
180 // https://github.com/OpenCPN/OpenCPN/pull/4002/files
181 m_vpMouse = new PlugIn_ViewPort(pPlugIn->GetCurrentViewPort());
182 pReq_Dialog = nullptr;
183 m_bGRIBActiveFile = nullptr;
184 m_pTimelineSet = nullptr;
185 m_gCursorData = nullptr;
186 m_gGRIBUICData = nullptr;
187 m_gtk_started = false;
188
189 wxFileConfig *pConf = GetOCPNConfigObject();
190
191 m_gGrabber = new GribGrabberWin(this); // add the grabber to the dialog
192 m_fgCtrlGrabberSize->Add(m_gGrabber, 0, wxALL, 0);
193
194 this->SetSizer(m_fgCtrlBarSizer);
195 this->Layout();
196 m_fgCtrlBarSizer->Fit(this);
197
198 // Initialize the time format check timer
199 m_tFormatRefresh.Connect(
200 wxEVT_TIMER, wxTimerEventHandler(GRIBUICtrlBar::OnFormatRefreshTimer),
201 nullptr, this);
202
203 // Sample the current time format using a fixed reference date (January 1,
204 // 2021 at noon)
205 wxDateTime referenceDate(1, wxDateTime::Jan, 2021, 12, 0, 0);
206 m_sLastTimeFormat = toUsrDateTimeFormat_Plugin(referenceDate);
207
208 // Start timer to check for time format changes (every 5 seconds)
209 m_tFormatRefresh.Start(5000);
210
211 if (pConf) {
212 pConf->SetPath("/Settings/GRIB");
213 pConf->Read("WindPlot", &m_bDataPlot[GribOverlaySettings::WIND], true);
214 pConf->Read("WindGustPlot", &m_bDataPlot[GribOverlaySettings::WIND_GUST],
215 false);
216 pConf->Read("PressurePlot", &m_bDataPlot[GribOverlaySettings::PRESSURE],
217 false);
218 pConf->Read("WavePlot", &m_bDataPlot[GribOverlaySettings::WAVE], false);
219 pConf->Read("CurrentPlot", &m_bDataPlot[GribOverlaySettings::CURRENT],
220 false);
221 pConf->Read("PrecipitationPlot",
222 &m_bDataPlot[GribOverlaySettings::PRECIPITATION], false);
223 pConf->Read("CloudPlot", &m_bDataPlot[GribOverlaySettings::CLOUD], false);
224 pConf->Read("AirTemperaturePlot",
225 &m_bDataPlot[GribOverlaySettings::AIR_TEMPERATURE], false);
226 pConf->Read("SeaTemperaturePlot",
227 &m_bDataPlot[GribOverlaySettings::SEA_TEMPERATURE], false);
228 pConf->Read("CAPEPlot", &m_bDataPlot[GribOverlaySettings::CAPE], false);
229 pConf->Read("CompReflectivityPlot",
230 &m_bDataPlot[GribOverlaySettings::COMP_REFL], false);
231
232 pConf->Read("CursorDataShown", &m_CDataIsShown, true);
233
234 pConf->Read("lastdatatype", &m_lastdatatype, 0);
235
236 pConf->SetPath("/Settings/GRIB/FileNames");
237 m_file_names.Clear();
238 if (pConf->GetNumberOfEntries()) {
239 wxString str, val;
240 long dummy;
241 bool bCont = pConf->GetFirstEntry(str, dummy);
242 while (bCont) {
243 pConf->Read(str, &val); // Get a file name
244 m_file_names.Add(val);
245 bCont = pConf->GetNextEntry(str, dummy);
246 }
247 }
248
249 wxStandardPathsBase &spath = wxStandardPaths::Get();
250
251 pConf->SetPath("/Directories");
252 pConf->Read("GRIBDirectory", &m_grib_dir);
253
254 pConf->SetPath("/PlugIns/GRIB");
255 pConf->Read("ManualRequestZoneSizing", &m_SavedZoneSelMode, 0);
256
257 // Read XyGrib related configuration
258 pConf->SetPath("/Settings/GRIB/XyGrib");
259 pConf->Read("AtmModelIndex", &xyGribConfig.atmModelIndex, 0);
260 pConf->Read("WaveModelIndex", &xyGribConfig.waveModelIndex, 0);
261 pConf->Read("ResolutionIndex", &xyGribConfig.resolutionIndex, 0);
262 pConf->Read("DurationIndex", &xyGribConfig.durationIndex, 0);
263 pConf->Read("RunIndex", &xyGribConfig.runIndex, 0);
264 pConf->Read("IntervalIndex", &xyGribConfig.intervalIndex, 0);
265 pConf->Read("Wind", &xyGribConfig.wind, true);
266 pConf->Read("Gust", &xyGribConfig.gust, true);
267 pConf->Read("Pressure", &xyGribConfig.pressure, false);
268 pConf->Read("Temperature", &xyGribConfig.temperature, true);
269 pConf->Read("Cape", &xyGribConfig.cape, false);
270 pConf->Read("Reflectivity", &xyGribConfig.reflectivity, false);
271 pConf->Read("CloudCover", &xyGribConfig.cloudCover, true);
272 pConf->Read("Precipitation", &xyGribConfig.precipitation, true);
273 pConf->Read("WaveHeight", &xyGribConfig.waveHeight, true);
274 pConf->Read("WindWaves", &xyGribConfig.windWaves, true);
275 }
276 // init zone selection parameters
278
279 // connect Timer
280 m_tPlayStop.Connect(wxEVT_TIMER,
281 wxTimerEventHandler(GRIBUICtrlBar::OnPlayStopTimer),
282 nullptr, this);
283 // connect functions
284 Connect(wxEVT_MOVE, wxMoveEventHandler(GRIBUICtrlBar::OnMove));
285
286 m_OverlaySettings.Read();
287
288 DimeWindow(this);
289
290 Fit();
291 SetMinSize(GetBestSize());
292 if (m_ProjectBoatPanel) {
293 m_ProjectBoatPanel->SetSpeed(pPlugIn->m_boat_sog);
294 m_ProjectBoatPanel->SetCourse(pPlugIn->m_boat_cog);
295 }
296 m_highlight_latmax = 0;
297 m_highlight_lonmax = 0;
298 m_highlight_latmin = 0;
299 m_highlight_lonmin = 0;
300
301#ifndef __ANDROID__
302 // Create the dialog for downloading GRIB files.
303 // This dialog is created here, but not shown until the user requests it.
304 // This is done such that user can draw bounding box on the chart before
305 // the dialog is shown.
306
307 createRequestDialog();
308#endif
309}
310
311GRIBUICtrlBar::~GRIBUICtrlBar() {
312 wxFileConfig *pConf = GetOCPNConfigObject();
313 ;
314
315 if (pConf) {
316 pConf->SetPath("/Settings/GRIB");
317 pConf->Write("WindPlot", m_bDataPlot[GribOverlaySettings::WIND]);
318 pConf->Write("WindGustPlot", m_bDataPlot[GribOverlaySettings::WIND_GUST]);
319 pConf->Write("PressurePlot", m_bDataPlot[GribOverlaySettings::PRESSURE]);
320 pConf->Write("WavePlot", m_bDataPlot[GribOverlaySettings::WAVE]);
321 pConf->Write("CurrentPlot", m_bDataPlot[GribOverlaySettings::CURRENT]);
322 pConf->Write("PrecipitationPlot",
323 m_bDataPlot[GribOverlaySettings::PRECIPITATION]);
324 pConf->Write("CloudPlot", m_bDataPlot[GribOverlaySettings::CLOUD]);
325 pConf->Write("AirTemperaturePlot",
326 m_bDataPlot[GribOverlaySettings::AIR_TEMPERATURE]);
327 pConf->Write("SeaTemperaturePlot",
328 m_bDataPlot[GribOverlaySettings::SEA_TEMPERATURE]);
329 pConf->Write("CAPEPlot", m_bDataPlot[GribOverlaySettings::CAPE]);
330 pConf->Write("CompReflectivityPlot",
331 m_bDataPlot[GribOverlaySettings::COMP_REFL]);
332
333 pConf->Write("CursorDataShown", m_CDataIsShown);
334
335 pConf->Write("lastdatatype", m_lastdatatype);
336
337 pConf->SetPath("/Settings/GRIB/FileNames");
338 int iFileMax = pConf->GetNumberOfEntries();
339 if (iFileMax) {
340 wxString key;
341 long dummy;
342 for (int i = 0; i < iFileMax; i++) {
343 if (pConf->GetFirstEntry(key, dummy)) pConf->DeleteEntry(key, false);
344 }
345 }
346
347 for (unsigned int i = 0; i < m_file_names.GetCount(); i++) {
348 wxString key;
349 key.Printf("Filename%d", i);
350 pConf->Write(key, m_file_names[i]);
351 }
352
353 pConf->SetPath("/Directories");
354 pConf->Write("GRIBDirectory", m_grib_dir);
355
356 // Write current XyGrib panel configuration to configuration file
357 pConf->SetPath("/Settings/GRIB/XyGrib");
358 pConf->Write("AtmModelIndex", xyGribConfig.atmModelIndex);
359 pConf->Write("WaveModelIndex", xyGribConfig.waveModelIndex);
360 pConf->Write("ResolutionIndex", xyGribConfig.resolutionIndex);
361 pConf->Write("DurationIndex", xyGribConfig.durationIndex);
362 pConf->Write("RunIndex", xyGribConfig.runIndex);
363 pConf->Write("IntervalIndex", xyGribConfig.intervalIndex);
364 pConf->Write("Wind", xyGribConfig.wind);
365 pConf->Write("Gust", xyGribConfig.gust);
366 pConf->Write("Pressure", xyGribConfig.pressure);
367 pConf->Write("Temperature", xyGribConfig.temperature);
368 pConf->Write("Cape", xyGribConfig.cape);
369 pConf->Write("Reflectivity", xyGribConfig.reflectivity);
370 pConf->Write("CloudCover", xyGribConfig.cloudCover);
371 pConf->Write("Precipitation", xyGribConfig.precipitation);
372 pConf->Write("WaveHeight", xyGribConfig.waveHeight);
373 pConf->Write("WindWaves", xyGribConfig.windWaves);
374 }
375 delete m_vpMouse;
376 delete m_pTimelineSet;
377}
378
379void GRIBUICtrlBar::SetScaledBitmap(double factor) {
380 // Round to the nearest "quarter", to avoid rendering artifacts
381 m_ScaledFactor = wxRound(factor * 4.0) / 4.0;
382 // set buttons bitmap
383 m_bpPrev->SetBitmapLabel(
384 GetScaledBitmap(wxBitmap(prev), "prev", m_ScaledFactor));
385 m_bpNext->SetBitmapLabel(
386 GetScaledBitmap(wxBitmap(next), "next", m_ScaledFactor));
387 m_bpAltitude->SetBitmapLabel(
388 GetScaledBitmap(wxBitmap(altitude), "altitude", m_ScaledFactor));
389 m_bpNow->SetBitmapLabel(
390 GetScaledBitmap(wxBitmap(now), "now", m_ScaledFactor));
391 m_bpZoomToCenter->SetBitmapLabel(
392 GetScaledBitmap(wxBitmap(zoomto), "zoomto", m_ScaledFactor));
393 m_bpPlay->SetBitmapLabel(
394 GetScaledBitmap(wxBitmap(play), "play", m_ScaledFactor));
395 m_bpShowCursorData->SetBitmapLabel(
396 GetScaledBitmap(wxBitmap(m_CDataIsShown ? curdata : ncurdata),
397 m_CDataIsShown ? "curdata" : "ncurdata", m_ScaledFactor));
398 if (m_bpOpenFile)
399 m_bpOpenFile->SetBitmapLabel(
400 GetScaledBitmap(wxBitmap(openfile), "openfile", m_ScaledFactor));
401 m_bpSettings->SetBitmapLabel(
402 GetScaledBitmap(wxBitmap(setting), "setting", m_ScaledFactor));
403
405
406 // Careful here, this MinSize() sets the final width of the control bar,
407 // overriding the width of the wxChoice above it.
408#ifdef __ANDROID__
409 m_sTimeline->SetSize(wxSize(20 * m_ScaledFactor, -1));
410 m_sTimeline->SetMinSize(wxSize(20 * m_ScaledFactor, -1));
411#else
412 m_sTimeline->SetSize(wxSize(90 * m_ScaledFactor, -1));
413 m_sTimeline->SetMinSize(wxSize(90 * m_ScaledFactor, -1));
414#endif
415}
416
418 if (nullptr == m_bpRequest) return;
419 m_bpRequest->SetBitmapLabel(
420 GetScaledBitmap(wxBitmap(request), "request", m_ScaledFactor));
421 m_bpRequest->SetToolTip(_("Start a download request"));
422}
423
424void GRIBUICtrlBar::OpenFile(bool newestFile) {
425 m_bpPlay->SetBitmapLabel(
426 GetScaledBitmap(wxBitmap(play), "play", m_ScaledFactor));
427 m_cRecordForecast->Clear();
428 pPlugIn->GetGRIBOverlayFactory()->ClearParticles();
429 m_Altitude = 0;
430 m_FileIntervalIndex = m_OverlaySettings.m_SlicesPerUpdate;
431 delete m_bGRIBActiveFile;
432 delete m_pTimelineSet;
433 m_pTimelineSet = nullptr;
434 m_sTimeline->SetValue(0);
435 m_TimeLineHours = 0;
436 m_InterpolateMode = false;
437 m_pNowMode = false;
438 m_SelectionIsSaved = false;
439 m_HasAltitude = false;
440
441 // get more recent file in default directory if necessary
442 wxFileName f;
443 if (newestFile)
444 m_file_names.Clear(); // file names list must be cleared if we expect only
445 // the newest file! otherwise newest file is added to
446 // the previously recorded, what we don't want
447 if (m_file_names.IsEmpty()) { // in any case were there is no filename
448 // previously recorded, we must take the newest
449 m_file_names = GetFilesInDirectory();
450 newestFile = true;
451 }
452
454 pPlugIn->GetCopyMissWaveRec(), newestFile);
455
456 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
457 wxString title;
458 if (m_bGRIBActiveFile->IsOK()) {
459 wxFileName fn(m_bGRIBActiveFile->GetFileNames()[0]);
460 title = (_("File: "));
461 title.Append(fn.GetFullName());
462 if (rsa->GetCount() == 0) { // valid but empty file
463 delete m_bGRIBActiveFile;
464 m_bGRIBActiveFile = nullptr;
465 title.Prepend(_("Error! ")).Append(_(" contains no valid data!"));
466 } else {
467 PopulateComboDataList();
468 title.append(" (" +
470 wxDateTime(m_bGRIBActiveFile->GetRefDateTime())) +
471 ")");
472
473 if (rsa->GetCount() > 1) {
474 GribRecordSet &first = rsa->Item(0), &second = rsa->Item(1),
475 &last = rsa->Item(rsa->GetCount() - 1);
476
477 // compute ntotal time span
478 wxTimeSpan span = wxDateTime(last.m_Reference_Time) -
479 wxDateTime(first.m_Reference_Time);
480 m_TimeLineHours = span.GetHours();
481
482 // get file interval index and update intervale choice if necessary
483 int halfintermin(wxTimeSpan(wxDateTime(second.m_Reference_Time) -
484 wxDateTime(first.m_Reference_Time))
485 .GetMinutes() /
486 2);
487 for (m_FileIntervalIndex = 0;; m_FileIntervalIndex++) {
488 if (m_OverlaySettings.GetMinFromIndex(m_FileIntervalIndex) >
489 halfintermin)
490 break;
491 }
492 if (m_FileIntervalIndex > 0) m_FileIntervalIndex--;
493 if (m_OverlaySettings.m_SlicesPerUpdate > m_FileIntervalIndex)
494 m_OverlaySettings.m_SlicesPerUpdate = m_FileIntervalIndex;
495 }
496 }
497 } else {
498 delete m_bGRIBActiveFile;
499 m_bGRIBActiveFile = nullptr;
500 title = _("No valid GRIB file");
501 }
502 pPlugIn->GetGRIBOverlayFactory()->SetMessage(title);
503 SetTitle(title);
504 SetTimeLineMax(false);
505 SetFactoryOptions();
506 if (pPlugIn->GetStartOptions() &&
507 m_TimeLineHours != 0) // fix a crash for one date files
508 ComputeBestForecastForNow();
509 else
510 TimelineChanged();
511
512 // populate altitude choice and show if necessary
514 for (int i = 1; i < 5; i++) {
515 if (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VX + i) !=
516 wxNOT_FOUND &&
517 m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VY + i) !=
518 wxNOT_FOUND)
519 m_HasAltitude = true;
520 }
521 m_Altitude = 0; // set altitude at std
522
523 // enable buttons according with file contents to ovoid crashes
524#ifdef __ANDROID__
525 m_bpSettings->Enable(true);
526#else
527 m_bpSettings->Enable(m_pTimelineSet != nullptr);
528#endif
529 m_bpZoomToCenter->Enable(m_pTimelineSet != nullptr);
530
531 m_sTimeline->Enable(m_pTimelineSet != nullptr && m_TimeLineHours);
532 m_bpPlay->Enable(m_pTimelineSet != nullptr && m_TimeLineHours);
533
534 m_bpPrev->Enable(m_pTimelineSet != nullptr && m_TimeLineHours);
535 m_bpNext->Enable(m_pTimelineSet != nullptr && m_TimeLineHours);
536 m_bpNow->Enable(m_pTimelineSet != nullptr && m_TimeLineHours);
537
538 SetCanvasContextMenuItemViz(pPlugIn->m_MenuItem, m_TimeLineHours != 0);
539
540 //
541 if (m_bGRIBActiveFile == nullptr) {
542 // there's no data we can use in this file
543 return;
544 }
545 // Try to verify that there will be at least one parameter in the GRIB file
546 // that is enabled for display This will ensure that at least "some" data is
547 // displayed on file change, and so avoid user confusion of no data shown.
548 // This is especially important if cursor tracking of data is disabled.
549
550 bool bconfigOK = false;
551 if (m_bDataPlot[GribOverlaySettings::WIND] &&
552 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VX) != wxNOT_FOUND))
553 bconfigOK = true;
554 if (m_bDataPlot[GribOverlaySettings::WIND_GUST] &&
555 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_GUST) != wxNOT_FOUND))
556 bconfigOK = true;
557 if (m_bDataPlot[GribOverlaySettings::PRESSURE] &&
558 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_PRESSURE) != wxNOT_FOUND))
559 bconfigOK = true;
560 if (m_bDataPlot[GribOverlaySettings::WAVE] &&
561 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WVDIR) != wxNOT_FOUND))
562 bconfigOK = true;
563 if (m_bDataPlot[GribOverlaySettings::WAVE] &&
564 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_HTSIGW) != wxNOT_FOUND))
565 bconfigOK = true;
566 if (m_bDataPlot[GribOverlaySettings::CURRENT] &&
567 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_SEACURRENT_VX) !=
568 wxNOT_FOUND))
569 bconfigOK = true;
570 if (m_bDataPlot[GribOverlaySettings::PRECIPITATION] &&
571 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_PRECIP_TOT) != wxNOT_FOUND))
572 bconfigOK = true;
573 if (m_bDataPlot[GribOverlaySettings::CLOUD] &&
574 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_CLOUD_TOT) != wxNOT_FOUND))
575 bconfigOK = true;
576 if (m_bDataPlot[GribOverlaySettings::AIR_TEMPERATURE] &&
577 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_AIR_TEMP) != wxNOT_FOUND))
578 bconfigOK = true;
579 if (m_bDataPlot[GribOverlaySettings::SEA_TEMPERATURE] &&
580 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_SEA_TEMP) != wxNOT_FOUND))
581 bconfigOK = true;
582 if (m_bDataPlot[GribOverlaySettings::CAPE] &&
583 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_CAPE) != wxNOT_FOUND))
584 bconfigOK = true;
585 if (m_bDataPlot[GribOverlaySettings::COMP_REFL] &&
586 (m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_COMP_REFL) != wxNOT_FOUND))
587 bconfigOK = true;
588
589 // If no parameter seems to be enabled by config, enable them all just to be
590 // sure something shows.
591 if (!bconfigOK) {
592 for (int i = 0; i < (int)GribOverlaySettings::GEO_ALTITUDE; i++) {
593 if (InDataPlot(i)) {
594 m_bDataPlot[i] = true;
595 }
596 }
597 }
598}
599
600bool GRIBUICtrlBar::GetGribZoneLimits(GribTimelineRecordSet *timelineSet,
601 double *latmin, double *latmax,
602 double *lonmin, double *lonmax) {
603 // calculate the largest overlay size
604 GribRecord **pGR = timelineSet->m_GribRecordPtrArray;
605 double ltmi = -GRIB_NOTDEF, ltma = GRIB_NOTDEF, lnmi = -GRIB_NOTDEF,
606 lnma = GRIB_NOTDEF;
607 for (unsigned int i = 0; i < Idx_COUNT; i++) {
608 GribRecord *pGRA = pGR[i];
609 if (!pGRA) continue;
610 if (pGRA->GetLatMin() < ltmi) ltmi = pGRA->GetLatMin();
611 if (pGRA->GetLatMax() > ltma) ltma = pGRA->GetLatMax();
612 if (pGRA->GetLonMin() < lnmi) lnmi = pGRA->GetLonMin();
613 if (pGRA->GetLonMax() > lnma) lnma = pGRA->GetLonMax();
614 }
615 if (ltmi == -GRIB_NOTDEF || lnmi == -GRIB_NOTDEF || ltma == GRIB_NOTDEF ||
616 lnma == GRIB_NOTDEF)
617 return false;
618
619 if (latmin) *latmin = ltmi;
620 if (latmax) *latmax = ltma;
621 if (lonmin) *lonmin = lnmi;
622 if (lonmax) *lonmax = lnma;
623 return true;
624}
625
626class FileCollector : public wxDirTraverser {
627public:
628 FileCollector(wxArrayString &files, const wxRegEx &pattern)
629 : m_files(files), m_pattern(pattern) {}
630 virtual wxDirTraverseResult OnFile(const wxString &filename) {
631 if (m_pattern.Matches(filename)) m_files.Add(filename);
632 return wxDIR_CONTINUE;
633 }
634 virtual wxDirTraverseResult OnDir(const wxString &WXUNUSED(dirname)) {
635 return wxDIR_IGNORE;
636 }
637
638private:
639 wxArrayString &m_files;
640 const wxRegEx &m_pattern;
641};
642
643wxArrayString GRIBUICtrlBar::GetFilesInDirectory() {
644 wxArrayString file_array;
645 if (!wxDir::Exists(m_grib_dir)) return file_array;
646
647 // Get an array of GRIB file names in the target directory, not descending
648 // into subdirs
649 wxRegEx pattern(".+\\.gri?b2?(\\.(bz2|gz))?$",
650 wxRE_EXTENDED | wxRE_ICASE | wxRE_NOSUB);
651 FileCollector collector(file_array, pattern);
652 wxDir dir(m_grib_dir);
653 dir.Traverse(collector);
654 file_array.Sort(
655 CompareFileStringTime); // sort the files by File Modification Date
656 return file_array;
657}
658
659void GRIBUICtrlBar::SetCursorLatLon(double lat, double lon) {
660 m_cursor_lon = lon;
661 m_cursor_lat = lat;
662
663 if (m_vpMouse && ((lat > m_vpMouse->lat_min) && (lat < m_vpMouse->lat_max)) &&
664 ((lon > m_vpMouse->lon_min) && (lon < m_vpMouse->lon_max)))
666}
667
669 if (!m_CDataIsShown) return;
670
671 if (m_DialogStyle >> 1 == SEPARATED) {
672 if (m_gGRIBUICData) {
673 if (!m_gGRIBUICData->m_gCursorData->m_tCursorTrackTimer.IsRunning())
674 m_gGRIBUICData->m_gCursorData->m_tCursorTrackTimer.Start(
675 50, wxTIMER_ONE_SHOT);
676 }
677 } else {
678 if (m_gCursorData) {
679 if (!m_gCursorData->m_tCursorTrackTimer.IsRunning())
680 m_gCursorData->m_tCursorTrackTimer.Start(50, wxTIMER_ONE_SHOT);
681 }
682 }
683}
684
685void GRIBUICtrlBar::OnShowCursorData(wxCommandEvent &event) {
686 m_CDataIsShown = !m_CDataIsShown;
687 m_bpShowCursorData->SetBitmapLabel(
688 GetScaledBitmap(wxBitmap(m_CDataIsShown ? curdata : ncurdata),
689 m_CDataIsShown ? "curdata" : "ncurdata", m_ScaledFactor));
690 SetDialogsStyleSizePosition(true);
691}
692
693void GRIBUICtrlBar::SetDialogsStyleSizePosition(bool force_recompute) {
694 /*Not all plateforms accept the dynamic window style changes.
695 So these changes are applied only after exit from the plugin and re-opening
696 it*/
697
698 if (!force_recompute &&
699 (m_old_DialogStyle == m_DialogStyle // recompute only if necessary
700 ||
701 (m_old_DialogStyle >> 1 == ATTACHED && m_DialogStyle >> 1 == ATTACHED)))
702 return;
703
704 bool m_HasCaption = GetWindowStyleFlag() == (wxCAPTION | wxCLOSE_BOX |
705 wxSYSTEM_MENU | wxTAB_TRAVERSAL);
706
707 /* first hide grabber, detach cursordata and set ctrl/buttons visibility to
708 have CtrlBar in his "alone" version altitude button visibility is a special
709 case ( i == 0 ) */
710 int state = (m_DialogStyle >> 1 == ATTACHED && m_CDataIsShown) ? 0 : 1;
711 for (unsigned i = 0; i < m_OverlaySettings.m_iCtrlBarCtrlVisible[state].Len();
712 i++) {
713 bool vis = i > 0 ? true : m_HasAltitude ? true : false;
714 if (FindWindow(i + ID_CTRLALTITUDE))
715 FindWindow(i + ID_CTRLALTITUDE)
716 ->Show(m_OverlaySettings.m_iCtrlBarCtrlVisible[state].GetChar(i) ==
717 'X' &&
718 vis);
719 }
720 // initiate tooltips
721 m_bpShowCursorData->SetToolTip(m_CDataIsShown ? _("Hide data at cursor")
722 : _("Show data at cursor"));
723 m_bpPlay->SetToolTip(_("Start play back"));
724
725 m_gGrabber->Hide();
726 // then hide and detach cursor data window
727 if (m_gCursorData) {
728 m_gCursorData->Hide();
729 m_fgCDataSizer->Detach(m_gCursorData);
730 }
731
732 SetMinSize(wxSize(0, 0));
733
734 // then cancel eventually Cursor data dialog (to be re-created later if
735 // necessary )
736 if (m_gGRIBUICData) {
737 m_gGRIBUICData->Destroy();
738 m_gGRIBUICData = nullptr;
739 }
740
741 if ((m_DialogStyle >> 1 == SEPARATED || !m_CDataIsShown) &&
742 !m_HasCaption) { // Size and show grabber if necessary
743 Fit(); // each time CtrlData dialog will be alone
744 m_gGrabber->Size(); // or separated
745 m_gGrabber->Show();
746 }
747
748 if (m_CDataIsShown) {
749 if (m_DialogStyle >> 1 == ATTACHED) { // dialogs attached
750 // generate CursorData
751 if (!m_gCursorData) m_gCursorData = new CursorData(this, *this);
752 pPlugIn->SetDialogFont(m_gCursorData);
753 m_gCursorData->PopulateTrackingControls(false);
754 // attach CursorData to CtrlBar if necessary
755 if (m_fgCDataSizer->GetItem(m_gCursorData) == nullptr)
756 m_fgCDataSizer->Add(m_gCursorData, 0);
757 m_gCursorData->Show();
758
759 } else if (m_DialogStyle >> 1 == SEPARATED) { // dialogs isolated
760 // create cursor data dialog
761 m_gGRIBUICData = new GRIBUICData(*this);
762 m_gGRIBUICData->m_gCursorData->PopulateTrackingControls(
763 m_DialogStyle == SEPARATED_VERTICAL);
764 pPlugIn->SetDialogFont(m_gGRIBUICData->m_gCursorData);
765 m_gGRIBUICData->Fit();
766 m_gGRIBUICData->Update();
767 m_gGRIBUICData->Show();
768 pPlugIn->MoveDialog(m_gGRIBUICData, pPlugIn->GetCursorDataXY());
769 m_gGRIBUICData->Layout();
770 m_gGRIBUICData->Fit();
771 }
772 }
773 Layout();
774 Fit();
775 wxSize sd = GetSize();
776#ifdef __WXGTK__
777 if (!m_gtk_started && m_HasCaption /*&& sd.y == GetSize().y*/) {
778 sd.y += 30;
779 m_gtk_started = true;
780 }
781#endif
782 SetSize(wxSize(sd.x, sd.y));
783 SetMinSize(wxSize(sd.x, sd.y));
784#ifdef __ANDROID__
785 wxRect tbRect = GetMasterToolbarRect();
786 // qDebug() << "TBR" << tbRect.x << tbRect.y << tbRect.width << tbRect.height
787 // << pPlugIn->GetCtrlBarXY().x << pPlugIn->GetCtrlBarXY().y;
788
789 if (1) {
790 wxPoint pNew = pPlugIn->GetCtrlBarXY();
791 pNew.x = tbRect.x + tbRect.width + 4;
792 pNew.y = 0; // tbRect.y;
793 pPlugIn->SetCtrlBarXY(pNew);
794 // qDebug() << "pNew" << pNew.x;
795
796 int widthAvail =
797 GetCanvasByIndex(0)->GetClientSize().x - (tbRect.x + tbRect.width);
798
799 if (sd.x > widthAvail) {
800 // qDebug() << "Too big" << widthAvail << sd.x;
801
802 int target_char_width = (float)widthAvail / 28;
803 wxScreenDC dc;
804 bool bOK = false;
805 int pointSize = 20;
806 int width, height;
807 wxFont *sFont;
808 while (!bOK) {
809 // qDebug() << "PointSize" << pointSize;
810 sFont = FindOrCreateFont_PlugIn(pointSize, wxFONTFAMILY_DEFAULT,
811 wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
812 FALSE);
813 dc.GetTextExtent("W", &width, &height, nullptr, nullptr, sFont);
814 if (width <= target_char_width) bOK = true;
815 pointSize--;
816 if (pointSize <= 10) bOK = true;
817 }
818
819 m_cRecordForecast->SetFont(*sFont);
820
821 Layout();
822 Fit();
823 Hide();
824 SetSize(wxSize(widthAvail, sd.y));
825 SetMinSize(wxSize(widthAvail, sd.y));
826 Show();
827 }
828 }
829 wxPoint pNow = pPlugIn->GetCtrlBarXY();
830 pNow.y = 0;
831 pPlugIn->SetCtrlBarXY(pNow);
832
833#endif
834
835 pPlugIn->MoveDialog(this, pPlugIn->GetCtrlBarXY());
836 m_old_DialogStyle = m_DialogStyle;
837}
838
839void GRIBUICtrlBar::OnAltitude(wxCommandEvent &event) {
840 if (!m_HasAltitude) return;
841
842 wxMenu *amenu = new wxMenu();
843 amenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
844 wxMenuEventHandler(GRIBUICtrlBar::OnMenuEvent), nullptr, this);
845
846 for (int i = 0; i < 5; i++) {
847 if (((m_pTimelineSet &&
848 m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VX + i) !=
849 wxNOT_FOUND &&
850 m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VY + i) !=
851 wxNOT_FOUND)) ||
852 i == 0) {
853 MenuAppend(
854 amenu, ID_CTRLALTITUDE + 1000 + i,
855
856 m_OverlaySettings.GetAltitudeFromIndex(
857 i, m_OverlaySettings.Settings[GribOverlaySettings::PRESSURE]
858 .m_Units),
859 wxITEM_RADIO);
860 }
861 }
862
863 amenu->Check(ID_CTRLALTITUDE + 1000 + m_Altitude, true);
864
865 PopupMenu(amenu);
866
867 delete amenu;
868}
869
870void GRIBUICtrlBar::OnMove(wxMoveEvent &event) {
871 int w, h;
872 GetScreenPosition(&w, &h);
873 pPlugIn->SetCtrlBarXY(wxPoint(w, h));
874}
875
876void GRIBUICtrlBar::OnMenuEvent(wxMenuEvent &event) {
877 int id = event.GetId();
878 wxCommandEvent evt;
879 evt.SetId(id);
880 int alt = m_Altitude;
881 switch (id) {
882 // sub menu altitude data
883 case ID_CTRLALTITUDE + 1000:
884 m_Altitude = 0;
885 break;
886 case ID_CTRLALTITUDE + 1001:
887 m_Altitude = 1;
888 break;
889 case ID_CTRLALTITUDE + 1002:
890 m_Altitude = 2;
891 break;
892 case ID_CTRLALTITUDE + 1003:
893 m_Altitude = 3;
894 break;
895 case ID_CTRLALTITUDE + 1004:
896 m_Altitude = 4;
897 break;
898 // end sub menu
899 case ID_BTNNOW:
900 OnNow(evt);
901 break;
902 case ID_BTNZOOMTC:
903 OnZoomToCenterClick(evt);
904 break;
905 case ID_BTNSHOWCDATA:
906 OnShowCursorData(evt);
907 break;
908 case ID_BTNPLAY:
909 OnPlayStop(evt);
910 break;
911 case ID_BTNOPENFILE:
912 OnOpenFile(evt);
913 break;
914 case ID_BTNSETTING:
915 OnSettings(evt);
916 break;
917 case ID_BTNREQUEST:
919 }
920 if (alt != m_Altitude) {
921 SetDialogsStyleSizePosition(true);
922 SetFactoryOptions(); // Reload the visibility options
923 }
924}
925
926void GRIBUICtrlBar::MenuAppend(wxMenu *menu, int id, wxString label,
927 wxItemKind kind, wxBitmap bitmap,
928 wxMenu *submenu) {
929 wxMenuItem *item = new wxMenuItem(menu, id, label, "", kind);
930 // add a submenu to this item if necessary
931 if (submenu) {
932 item->SetSubMenu(submenu);
933 }
934
935 /* Menu font do not work properly for MSW (wxWidgets 3.2.1)
936 #ifdef __WXMSW__
937 wxFont *qFont = OCPNGetFont(_("Menu"), 0);
938 item->SetFont(*qFont);
939 #endif
940 */
941
942#if defined(__WXMSW__) || defined(__WXGTK__)
943 if (!bitmap.IsSameAs(wxNullBitmap)) item->SetBitmap(bitmap);
944#endif
945
946 menu->Append(item);
947}
948
949void GRIBUICtrlBar::OnMouseEvent(wxMouseEvent &event) {
950 if (event.RightDown()) {
951 // populate menu
952 wxMenu *xmenu = new wxMenu();
953 xmenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
954 wxMenuEventHandler(GRIBUICtrlBar::OnMenuEvent), nullptr,
955 this);
956
957 if (m_HasAltitude) { // eventually populate altitude choice
958 wxMenu *smenu = new wxMenu();
959 smenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
960 wxMenuEventHandler(GRIBUICtrlBar::OnMenuEvent), nullptr,
961 this);
962
963 for (int i = 0; i < 5; i++) {
964 if (((m_pTimelineSet &&
965 m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VX + i) !=
966 wxNOT_FOUND &&
967 m_bGRIBActiveFile->m_GribIdxArray.Index(Idx_WIND_VY + i) !=
968 wxNOT_FOUND)) ||
969 i == 0) {
970 MenuAppend(
971 smenu, ID_CTRLALTITUDE + 1000 + i,
972 m_OverlaySettings.GetAltitudeFromIndex(
973 i, m_OverlaySettings.Settings[GribOverlaySettings::PRESSURE]
974 .m_Units),
975 wxITEM_RADIO);
976 }
977 }
978 smenu->Check(ID_CTRLALTITUDE + 1000 + m_Altitude, true);
979 MenuAppend(
980 xmenu, wxID_ANY, _("Select geopotential altitude"), wxITEM_NORMAL,
981 GetScaledBitmap(wxBitmap(altitude), "altitude", m_ScaledFactor),
982 smenu);
983 }
984 MenuAppend(xmenu, ID_BTNNOW, _("Now"), wxITEM_NORMAL,
985 GetScaledBitmap(wxBitmap(now), "now", m_ScaledFactor));
986 MenuAppend(xmenu, ID_BTNZOOMTC, _("Zoom To Center"), wxITEM_NORMAL,
987 GetScaledBitmap(wxBitmap(zoomto), "zoomto", m_ScaledFactor));
988 MenuAppend(
989 xmenu, ID_BTNSHOWCDATA,
990 m_CDataIsShown ? _("Hide data at cursor") : _("Show data at cursor"),
991 wxITEM_NORMAL,
992 GetScaledBitmap(wxBitmap(m_CDataIsShown ? curdata : ncurdata),
993 m_CDataIsShown ? "curdata" : "ncurdata",
994 m_ScaledFactor));
995 MenuAppend(
996 xmenu, ID_BTNPLAY,
997 m_tPlayStop.IsRunning() ? _("Stop play back") : _("Start play back"),
998 wxITEM_NORMAL,
999 GetScaledBitmap(wxBitmap(m_tPlayStop.IsRunning() ? stop : play),
1000 m_tPlayStop.IsRunning() ? "stop" : "play",
1001 m_ScaledFactor));
1002 MenuAppend(xmenu, ID_BTNOPENFILE, _("Open a new file"), wxITEM_NORMAL,
1003 GetScaledBitmap(wxBitmap(openfile), "openfile", m_ScaledFactor));
1004 MenuAppend(xmenu, ID_BTNSETTING, _("Settings"), wxITEM_NORMAL,
1005 GetScaledBitmap(wxBitmap(setting), "setting", m_ScaledFactor));
1006 bool requeststate1 = m_ZoneSelMode == AUTO_SELECTION ||
1009 bool requeststate3 = m_ZoneSelMode == DRAW_SELECTION;
1010 MenuAppend(xmenu, ID_BTNREQUEST,
1011 requeststate1 ? _("Request forecast data")
1012 : requeststate3
1013 ? _("Draw requested Area or Click here to stop request")
1014 : _("Valid Area and Continue"),
1015 wxITEM_NORMAL,
1016 GetScaledBitmap(wxBitmap(requeststate1 ? request
1017 : requeststate3 ? selzone
1018 : request_end),
1019 requeststate1 ? "request"
1020 : requeststate3 ? "selzone"
1021 : "request_end",
1022 m_ScaledFactor));
1023
1024 PopupMenu(xmenu);
1025
1026 delete xmenu;
1027
1028 return;
1029 }
1030
1031 if (m_DialogStyle >> 1 == SEPARATED) return;
1032 wxMouseEvent evt(event);
1033 evt.SetId(1000);
1034
1035#ifndef __ANDROID__
1036 if (m_gCursorData && m_CDataIsShown) {
1037 m_gCursorData->OnMouseEvent(evt);
1038 }
1039#endif
1040}
1041
1042void GRIBUICtrlBar::ContextMenuItemCallback(int id) {
1043 // deactivate cursor data update during menu callback
1044 bool dataisshown = m_CDataIsShown;
1045 m_CDataIsShown = false;
1046 //
1047 wxFileConfig *pConf = GetOCPNConfigObject();
1048
1049 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1050 GRIBTable *table = new GRIBTable(*this);
1051
1052 table->InitGribTable(rsa, GetNearestIndex(GetNow(), 0));
1053 table->SetTableSizePosition(m_vpMouse->pix_width, m_vpMouse->pix_height);
1054
1055 table->ShowModal();
1056
1057 // re-activate cursor data
1058 m_CDataIsShown = dataisshown;
1059 delete table;
1060}
1061
1063 if (m_vpMouse == vp) return;
1064
1065 delete m_vpMouse;
1066 m_vpMouse = new PlugIn_ViewPort(*vp);
1067
1068 if (pReq_Dialog) pReq_Dialog->OnVpUnderMouseChange(vp);
1069}
1070
1072 if (pReq_Dialog) pReq_Dialog->OnVpWithFocusChange(vp);
1073}
1074
1075void GRIBUICtrlBar::OnClose(wxCloseEvent &event) {
1076 StopPlayBack();
1077 if (m_gGRIBUICData) m_gGRIBUICData->Hide();
1078 if (pReq_Dialog)
1080 pReq_Dialog->StopGraphicalZoneSelection();
1082 // SetRequestButtonBitmap( m_ZoneSelMode );
1083 }
1084 pPlugIn->SendTimelineMessage(wxInvalidDateTime);
1085
1086 pPlugIn->OnGribCtrlBarClose();
1087}
1088
1089void GRIBUICtrlBar::OnSize(wxSizeEvent &event) {
1090 // Record the dialog size
1091 wxSize p = event.GetSize();
1092 pPlugIn->SetCtrlBarSizeXY(p);
1093
1094 event.Skip();
1095}
1096
1097void GRIBUICtrlBar::OnPaint(wxPaintEvent &event) {
1098 wxWindowListNode *node = this->GetChildren().GetFirst();
1099 wxPaintDC dc(this);
1100 while (node) {
1101 wxWindow *win = node->GetData();
1102 if (dynamic_cast<wxBitmapButton *>(win))
1103 dc.DrawBitmap(dynamic_cast<wxBitmapButton *>(win)->GetBitmap(), 5, 5,
1104 false);
1105 node = node->GetNext();
1106 }
1107}
1108
1109void GRIBUICtrlBar::createRequestDialog() {
1110 ::wxBeginBusyCursor();
1111
1112 delete pReq_Dialog; // delete to be re-created
1113
1114 pReq_Dialog = new GribRequestSetting(*this);
1115 pPlugIn->SetDialogFont(pReq_Dialog);
1116 pPlugIn->SetDialogFont(pReq_Dialog->m_sScrolledDialog);
1117 pReq_Dialog->OnVpUnderMouseChange(m_vpMouse);
1118 pReq_Dialog->OnVpWithFocusChange(m_vpMouse);
1119 pReq_Dialog->SetRequestDialogSize();
1120 if (::wxIsBusy()) ::wxEndBusyCursor();
1121}
1122
1123void GRIBUICtrlBar::OnRequestForecastData(wxCommandEvent &event) {
1124 if (m_tPlayStop.IsRunning())
1125 return; // do nothing when play back is running !
1126
1127 /*if there is one instance of the dialog already visible, do nothing*/
1128 if (pReq_Dialog && pReq_Dialog->IsShown()) return;
1129
1130 /*create new request dialog*/
1131 createRequestDialog();
1132 // need to set a position at start
1133 int w;
1134 ::wxDisplaySize(&w, nullptr);
1135 pReq_Dialog->Move((w - pReq_Dialog->GetSize().GetX()) / 2, 30);
1136 pReq_Dialog->Show();
1137
1138 SetRequestButtonBitmap(m_ZoneSelMode); // set appopriate bitmap
1139}
1140
1141void GRIBUICtrlBar::OnSettings(wxCommandEvent &event) {
1142 if (m_tPlayStop.IsRunning())
1143 return; // do nothing when play back is running !
1144
1145 ::wxBeginBusyCursor();
1146
1149 *this, m_OverlaySettings, m_lastdatatype, m_FileIntervalIndex);
1150 // set font
1151 pPlugIn->SetDialogFont(dialog);
1152 for (size_t i = 0; i < dialog->m_nSettingsBook->GetPageCount(); i++) {
1153 wxScrolledWindow *sc =
1154 ((wxScrolledWindow *)dialog->m_nSettingsBook->GetPage(i));
1155 pPlugIn->SetDialogFont(sc);
1156 } // end set font
1157
1158 dialog->m_nSettingsBook->ChangeSelection(dialog->GetPageIndex());
1159 dialog->SetSettingsDialogSize();
1160 // need to set a position at start
1161 int w;
1162 ::wxDisplaySize(&w, nullptr);
1163 dialog->Move((w - dialog->GetSize().GetX()) / 2, 30);
1164 // end set position
1165
1166 ::wxEndBusyCursor();
1167
1168 if (dialog->ShowModal() == wxID_OK) {
1169 dialog->WriteSettings();
1170 m_OverlaySettings.Write();
1171 if (m_OverlaySettings.Settings[GribOverlaySettings::WIND].m_Units !=
1172 initSettings.Settings[GribOverlaySettings::WIND].m_Units &&
1173 (m_OverlaySettings.Settings[GribOverlaySettings::WIND].m_Units ==
1174 GribOverlaySettings::BFS ||
1175 initSettings.Settings[GribOverlaySettings::WIND].m_Units ==
1176 GribOverlaySettings::BFS))
1177 m_old_DialogStyle =
1178 STARTING_STATE_STYLE; // must recompute dialogs size if wind unit
1179 // have been changed
1180 } else {
1181 m_OverlaySettings = initSettings;
1182 m_DialogStyle = initSettings.m_iCtrlandDataStyle;
1183 }
1184 ::wxBeginBusyCursor();
1185
1186 dialog->SaveLastPage();
1187 if (!m_OverlaySettings.m_bInterpolate)
1188 m_InterpolateMode = false; // Interpolate could have been unchecked
1189 SetTimeLineMax(true);
1190 SetFactoryOptions();
1191
1192 SetDialogsStyleSizePosition(true);
1193 delete dialog;
1194
1195 event.Skip();
1196}
1197
1198#ifdef __ANDROID__
1199wxString callActivityMethod_ss(const char *method, wxString parm);
1200#endif
1201
1202void GRIBUICtrlBar::OnCompositeDialog(wxCommandEvent &event) {
1203 // Grab the current settings values
1205 initSettings.Read();
1206
1207 wxString json;
1208 wxString json_begin = initSettings.SettingsToJSON(json);
1209 wxLogMessage(json_begin);
1210
1211 // Pick up the required options from the Request dialog
1212 // and add them to the JSON object
1213 // Really, this just means the current viewport coordinates.
1214 // Everything else is stored in Android app preferences bundle.
1215
1216 PlugIn_ViewPort current_vp = pPlugIn->GetCurrentViewPort();
1217
1218 double lon_min = wxRound(current_vp.lon_min) - 1;
1219 double lon_max = wxRound(current_vp.lon_max) + 1;
1220 double lat_min = wxRound(current_vp.lat_min) - 1;
1221 double lat_max = wxRound(current_vp.lat_max) + 1;
1222
1223 wxJSONValue v;
1224 wxJSONReader reader;
1225 int numErrors = reader.Parse(json_begin, &v);
1226 if (numErrors > 0) {
1227 return;
1228 }
1229
1230 v["latMin"] = lat_min;
1231 v["latMax"] = lat_max;
1232 v["lonMin"] = lon_min;
1233 v["lonMax"] = lon_max;
1234
1235 // Clear the file name field, so that a retrieved or selected file name can
1236 // be returned
1237 v["grib_file"] = "";
1238
1239 wxJSONWriter w;
1240 wxString json_final;
1241 w.Write(v, json_final);
1242 wxLogMessage(json_final);
1243
1244#ifdef __ANDROID__
1245 wxString ret = callActivityMethod_ss("doGRIBActivity", json_final);
1246 wxLogMessage(ret);
1247#endif
1248
1249 event.Skip();
1250}
1251
1252void GRIBUICtrlBar::OpenFileFromJSON(wxString json) {
1253 // construct the JSON root object
1254 wxJSONValue root;
1255 // construct a JSON parser
1256 wxJSONReader reader;
1257
1258 int numErrors = reader.Parse(json, &root);
1259 if (numErrors > 0) {
1260 return;
1261 }
1262
1263 wxString file = root[("grib_file")].AsString();
1264
1265 if (file.Length() && wxFileExists(file)) {
1266 wxFileName fn(file);
1267 m_grib_dir = fn.GetPath();
1268 m_file_names.Clear();
1269 m_file_names.Add(file);
1270 OpenFile();
1271 }
1272}
1273
1274void GRIBUICtrlBar::OnPlayStop(wxCommandEvent &event) {
1275 if (m_tPlayStop.IsRunning()) {
1276 StopPlayBack();
1277 } else {
1278 m_bpPlay->SetBitmapLabel(
1279 GetScaledBitmap(wxBitmap(stop), "stop", m_ScaledFactor));
1280 m_bpPlay->SetToolTip(_("Stop play back"));
1281 m_tPlayStop.Start(3000 / m_OverlaySettings.m_UpdatesPerSecond,
1282 wxTIMER_CONTINUOUS);
1283 m_InterpolateMode = m_OverlaySettings.m_bInterpolate;
1284 }
1285}
1286
1287void GRIBUICtrlBar::OnPlayStopTimer(wxTimerEvent &event) {
1288 if (m_sTimeline->GetValue() >= m_sTimeline->GetMax()) {
1289 if (m_OverlaySettings.m_bLoopMode) {
1290 if (m_OverlaySettings.m_LoopStartPoint) {
1291 ComputeBestForecastForNow();
1292 if (m_sTimeline->GetValue() >= m_sTimeline->GetMax())
1293 StopPlayBack(); // will stop playback
1294 return;
1295 } else
1296 m_sTimeline->SetValue(0);
1297 } else {
1298 StopPlayBack(); // will stop playback
1299 return;
1300 }
1301 } else {
1302 int value = m_pNowMode ? m_OverlaySettings.m_bInterpolate
1303 ? GetNearestValue(GetNow(), 1)
1304 : GetNearestIndex(GetNow(), 2)
1305 : m_sTimeline->GetValue();
1306 m_sTimeline->SetValue(value + 1);
1307 }
1308
1309 m_pNowMode = false;
1310 if (!m_InterpolateMode)
1311 m_cRecordForecast->SetSelection(m_sTimeline->GetValue());
1312 TimelineChanged();
1313}
1314
1315void GRIBUICtrlBar::StopPlayBack() {
1316 if (m_tPlayStop.IsRunning()) {
1317 m_tPlayStop.Stop();
1318 m_bpPlay->SetBitmapLabel(
1319 GetScaledBitmap(wxBitmap(play), "play", m_ScaledFactor));
1320 m_bpPlay->SetToolTip(_("Start play back"));
1321 }
1322}
1323
1324void GRIBUICtrlBar::TimelineChanged() {
1326 pPlugIn->GetGRIBOverlayFactory()->SetGribTimelineRecordSet(nullptr);
1327 return;
1328 }
1329
1330 RestaureSelectionString(); // eventually restaure the previousely saved time
1331 // label
1332
1333 wxDateTime time = TimelineTime();
1334 SetGribTimelineRecordSet(GetTimeLineRecordSet(time));
1335
1336 if (!m_InterpolateMode) {
1337 /* get closest value to update timeline */
1338 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1339 GribRecordSet &sel = rsa->Item(m_cRecordForecast->GetCurrentSelection());
1340 wxDateTime t = sel.m_Reference_Time;
1341 m_sTimeline->SetValue(m_OverlaySettings.m_bInterpolate
1342 ? wxTimeSpan(t - MinTime()).GetMinutes() /
1343 m_OverlaySettings.GetMinFromIndex(
1344 m_OverlaySettings.m_SlicesPerUpdate)
1345 : m_cRecordForecast->GetCurrentSelection());
1346 } else {
1347 m_cRecordForecast->SetSelection(GetNearestIndex(time, 2));
1348 SaveSelectionString(); // memorize index and label
1349 wxString formattedTime = toUsrDateTimeFormat_Plugin(time);
1350 m_cRecordForecast->SetString(m_Selection_index,
1351 formattedTime); // replace it by the
1352 // interpolated time label
1353 m_cRecordForecast->SetStringSelection(
1354 formattedTime); // ensure it's visible in the box
1355 }
1356
1358
1359 pPlugIn->SendTimelineMessage(time);
1360 RequestRefresh(GetGRIBCanvas());
1361}
1362
1363void GRIBUICtrlBar::RestaureSelectionString() {
1364 if (!m_SelectionIsSaved) return;
1365
1366 int sel = m_cRecordForecast->GetSelection();
1367 m_cRecordForecast->SetString(m_Selection_index, m_Selection_label);
1368 m_cRecordForecast->SetSelection(sel);
1369 m_SelectionIsSaved = false;
1370}
1371
1372int GRIBUICtrlBar::GetNearestIndex(wxDateTime time, int model) {
1373 /* get closest index to update combo box */
1374 size_t i;
1375 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1376
1377 wxDateTime itime, ip1time;
1378 for (i = 0; i < rsa->GetCount() - 1; i++) {
1379 itime = rsa->Item(i).m_Reference_Time;
1380 ip1time = rsa->Item(i + 1).m_Reference_Time;
1381 if (ip1time >= time) break;
1382 }
1383 if (!model) return (time - itime > (ip1time - time) * 3) ? i + 1 : i;
1384
1385 return model == 1 ? time == ip1time ? i : i + 1 : time == ip1time ? i + 1 : i;
1386}
1387
1388int GRIBUICtrlBar::GetNearestValue(wxDateTime time, int model) {
1389 /* get closest value to update Time line */
1390 if (m_TimeLineHours == 0) return 0;
1391 wxDateTime itime, ip1time;
1392 int stepmin =
1393 m_OverlaySettings.GetMinFromIndex(m_OverlaySettings.m_SlicesPerUpdate);
1394 wxTimeSpan span = time - MinTime();
1395 int t = span.GetMinutes() / stepmin;
1396 itime = MinTime() +
1397 wxTimeSpan(t * stepmin / 60, (t * stepmin) % 60); // time at t
1398 ip1time = itime + wxTimeSpan(stepmin / 60, stepmin % 60); // time at t+1
1399
1400 if (model == 1) return time == ip1time ? t + 1 : t;
1401
1402 return (time - itime > (ip1time - time) * 3) ? t + 1 : t;
1403}
1404
1405wxDateTime GRIBUICtrlBar::GetNow() {
1406 wxDateTime now = wxDateTime::Now();
1407 now.GetSecond(0);
1408
1409 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1410
1411 // Verify if we are outside of the file time range
1412 now = (now > rsa->Item(rsa->GetCount() - 1).m_Reference_Time)
1413 ? rsa->Item(rsa->GetCount() - 1).m_Reference_Time
1414 : (now < rsa->Item(0).m_Reference_Time) ? rsa->Item(0).m_Reference_Time
1415 : now;
1416 return now;
1417}
1418
1420 if (m_InterpolateMode) {
1421 int tl = (m_TimeLineHours == 0) ? 0 : m_sTimeline->GetValue();
1422 int stepmin =
1423 m_OverlaySettings.GetMinFromIndex(m_OverlaySettings.m_SlicesPerUpdate);
1424 return MinTime() + wxTimeSpan(tl * stepmin / 60, (tl * stepmin) % 60);
1425 }
1426
1427 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1428 unsigned int index = m_cRecordForecast->GetCurrentSelection() < 1
1429 ? 0
1430 : m_cRecordForecast->GetCurrentSelection();
1431 if (rsa && index < rsa->GetCount()) return rsa->Item(index).m_Reference_Time;
1432
1433 return wxDateTime::Now();
1434}
1435
1436wxDateTime GRIBUICtrlBar::MinTime() {
1437 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1438 if (rsa && rsa->GetCount()) {
1439 GribRecordSet &first = rsa->Item(0);
1440 return first.m_Reference_Time;
1441 }
1442 return wxDateTime::Now();
1443}
1444
1446 if (m_bGRIBActiveFile == nullptr) return nullptr;
1447 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1448
1449 if (rsa->GetCount() == 0) return nullptr;
1450
1452 new GribTimelineRecordSet(m_bGRIBActiveFile->GetCounter());
1453 for (int i = 0; i < Idx_COUNT; i++) {
1454 GribRecordSet *GRS1 = nullptr, *GRS2 = nullptr;
1455 GribRecord *GR1 = nullptr, *GR2 = nullptr;
1456 wxDateTime GR1time, GR2time;
1457
1458 // already computed using polar interpolation from first axis
1459 if (set->m_GribRecordPtrArray[i]) continue;
1460
1461 unsigned int j;
1462 for (j = 0; j < rsa->GetCount(); j++) {
1463 GribRecordSet *GRS = &rsa->Item(j);
1464 GribRecord *GR = GRS->m_GribRecordPtrArray[i];
1465 if (!GR) continue;
1466
1467 wxDateTime curtime = GRS->m_Reference_Time;
1468 if (curtime <= time) GR1time = curtime, GRS1 = GRS, GR1 = GR;
1469
1470 if (curtime >= time) {
1471 GR2time = curtime, GRS2 = GRS, GR2 = GR;
1472 break;
1473 }
1474 }
1475
1476 if (!GR1 || !GR2) continue;
1477
1478 wxDateTime mintime = MinTime();
1479 double minute2 = (GR2time - mintime).GetMinutes();
1480 double minute1 = (GR1time - mintime).GetMinutes();
1481 double nminute = (time - mintime).GetMinutes();
1482
1483 if (minute2 < minute1 || nminute < minute1 || nminute > minute2) continue;
1484
1485 double interp_const;
1486 if (minute1 == minute2) {
1487 // with big grib a copy is slow use a reference.
1488 set->m_GribRecordPtrArray[i] = GR1;
1489 continue;
1490 } else
1491 interp_const = (nminute - minute1) / (minute2 - minute1);
1492
1493 /* if this is a vector interpolation use the 2d method */
1494 if (i < Idx_WIND_VY) {
1495 GribRecord *GR1y = GRS1->m_GribRecordPtrArray[i + Idx_WIND_VY];
1496 GribRecord *GR2y = GRS2->m_GribRecordPtrArray[i + Idx_WIND_VY];
1497 if (GR1y && GR2y) {
1498 GribRecord *Ry;
1499 set->SetUnRefGribRecord(
1500 i, GribRecord::Interpolated2DRecord(Ry, *GR1, *GR1y, *GR2, *GR2y,
1501 interp_const));
1502 set->SetUnRefGribRecord(i + Idx_WIND_VY, Ry);
1503 continue;
1504 }
1505 } else if (i <= Idx_WIND_VY300)
1506 continue;
1507 else if (i == Idx_SEACURRENT_VX) {
1509 GribRecord *GR2y = GRS2->m_GribRecordPtrArray[Idx_SEACURRENT_VY];
1510 if (GR1y && GR2y) {
1511 GribRecord *Ry;
1512 set->SetUnRefGribRecord(
1513 i, GribRecord::Interpolated2DRecord(Ry, *GR1, *GR1y, *GR2, *GR2y,
1514 interp_const));
1516 continue;
1517 }
1518 } else if (i == Idx_SEACURRENT_VY)
1519 continue;
1520
1522 *GR1, *GR2, interp_const, i == Idx_WVDIR));
1523 }
1524
1525 set->m_Reference_Time = time.GetTicks();
1526 //(1-interp_const)*GRS1.m_Reference_Time + interp_const*GRS2.m_Reference_Time;
1527 return set;
1528}
1529
1531 wxPoint p(0, 0);
1532 auto now = TimelineTime();
1533 auto sog = m_ProjectBoatPanel->GetSpeed();
1534 auto cog = m_ProjectBoatPanel->GetCourse();
1535 double dist =
1536 static_cast<double>(now.GetTicks() - pPlugIn->m_boat_time) * sog / 3600.0;
1538 pPlugIn->m_boat_lon, cog, dist,
1539 &m_projected_lat, &m_projected_lon);
1540 if (vp) {
1541 GetCanvasPixLL(vp, &p, m_projected_lat, m_projected_lon);
1542 }
1543 x = p.x;
1544 y = p.y;
1545}
1546
1547double GRIBUICtrlBar::getTimeInterpolatedValue(int idx, double lon, double lat,
1548 wxDateTime time) {
1549 if (m_bGRIBActiveFile == nullptr) return GRIB_NOTDEF;
1550 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1551
1552 if (rsa->GetCount() == 0) return GRIB_NOTDEF;
1553
1554 GribRecord *before = nullptr, *after = nullptr;
1555
1556 unsigned int j;
1557 time_t t = time.GetTicks();
1558 for (j = 0; j < rsa->GetCount(); j++) {
1559 GribRecordSet *GRS = &rsa->Item(j);
1560 GribRecord *GR = GRS->m_GribRecordPtrArray[idx];
1561 if (!GR) continue;
1562
1563 time_t curtime = GR->GetRecordCurrentDate();
1564 if (curtime == t) return GR->GetInterpolatedValue(lon, lat);
1565
1566 if (curtime < t) before = GR;
1567
1568 if (curtime > t) {
1569 after = GR;
1570 break;
1571 }
1572 }
1573 // time_t wxDateTime::GetTicks();
1574 if (!before || !after) return GRIB_NOTDEF;
1575
1576 time_t t1 = before->GetRecordCurrentDate();
1577 time_t t2 = after->GetRecordCurrentDate();
1578 if (t1 == t2) return before->GetInterpolatedValue(lon, lat);
1579
1580 double v1 = before->GetInterpolatedValue(lon, lat);
1581 double v2 = after->GetInterpolatedValue(lon, lat);
1582 if (v1 != GRIB_NOTDEF && v2 != GRIB_NOTDEF) {
1583 double k = fabs((double)(t - t1) / (t2 - t1));
1584 return (1.0 - k) * v1 + k * v2;
1585 }
1586
1587 return GRIB_NOTDEF;
1588}
1589
1590bool GRIBUICtrlBar::getTimeInterpolatedValues(double &M, double &A, int idx1,
1591 int idx2, double lon, double lat,
1592 wxDateTime time) {
1593 M = GRIB_NOTDEF;
1594 A = GRIB_NOTDEF;
1595
1596 if (m_bGRIBActiveFile == nullptr) return false;
1597 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1598
1599 if (rsa->GetCount() == 0) return false;
1600
1601 GribRecord *beforeX = nullptr, *afterX = nullptr;
1602 GribRecord *beforeY = nullptr, *afterY = nullptr;
1603
1604 unsigned int j;
1605 time_t t = time.GetTicks();
1606 for (j = 0; j < rsa->GetCount(); j++) {
1607 GribRecordSet *GRS = &rsa->Item(j);
1608 GribRecord *GX = GRS->m_GribRecordPtrArray[idx1];
1609 GribRecord *GY = GRS->m_GribRecordPtrArray[idx2];
1610 if (!GX || !GY) continue;
1611
1612 time_t curtime = GX->GetRecordCurrentDate();
1613 if (curtime == t) {
1614 return GribRecord::GetInterpolatedValues(M, A, GX, GY, lon, lat, true);
1615 }
1616 if (curtime < t) {
1617 beforeX = GX;
1618 beforeY = GY;
1619 }
1620 if (curtime > t) {
1621 afterX = GX;
1622 afterY = GY;
1623 break;
1624 }
1625 }
1626 // time_t wxDateTime::GetTicks();
1627 if (!beforeX || !afterX) return false;
1628
1629 time_t t1 = beforeX->GetRecordCurrentDate();
1630 time_t t2 = afterX->GetRecordCurrentDate();
1631 if (t1 == t2) {
1632 return GribRecord::GetInterpolatedValues(M, A, beforeX, beforeY, lon, lat,
1633 true);
1634 }
1635 double v1m, v2m, v1a, v2a;
1636 if (!GribRecord::GetInterpolatedValues(v1m, v1a, beforeX, beforeY, lon, lat,
1637 true))
1638 return false;
1639
1640 if (!GribRecord::GetInterpolatedValues(v2m, v2a, afterX, afterY, lon, lat,
1641 true))
1642 return false;
1643
1644 if (v1m == GRIB_NOTDEF || v2m == GRIB_NOTDEF || v1a == GRIB_NOTDEF ||
1645 v2a == GRIB_NOTDEF)
1646 return false;
1647
1648 double k = fabs((double)(t - t1) / (t2 - t1));
1649 M = (1.0 - k) * v1m + k * v2m;
1650 A = (1.0 - k) * v1a + k * v2a;
1651 return true;
1652}
1653
1654void GRIBUICtrlBar::OnTimeline(wxScrollEvent &event) {
1655 StopPlayBack();
1656 m_InterpolateMode = m_OverlaySettings.m_bInterpolate;
1657 if (!m_InterpolateMode)
1658 m_cRecordForecast->SetSelection(m_sTimeline->GetValue());
1659 m_pNowMode = false;
1660 TimelineChanged();
1661}
1662
1663void GRIBUICtrlBar::OnOpenFile(wxCommandEvent &event) {
1664 if (m_tPlayStop.IsRunning())
1665 return; // do nothing when play back is running !
1666
1667#ifndef __ANDROID__
1668
1669 wxStandardPathsBase &path = wxStandardPaths::Get();
1670 wxString l_grib_dir = path.GetDocumentsDir();
1671
1672 if (wxDir::Exists(m_grib_dir)) l_grib_dir = m_grib_dir;
1673#ifdef __WXOSX__
1674 /* See Wildcard Filters section at
1675 https://docs.wxwidgets.org/3.2/classwx_file_dialog.html for why OSX needs
1676 this setting */
1677 // Save the current setting so that it can be restored when the dialog is
1678 // closed.
1679 auto saved_show_types_setting =
1680 wxSystemOptions::GetOptionInt(wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES);
1681 wxSystemOptions::SetOption(wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES, 1);
1682#endif // __WXOSX__
1683 wxFileDialog *dialog = new wxFileDialog(
1684 nullptr, _("Select a GRIB file"), l_grib_dir, "",
1685 _("Grib files") +
1686 " (*.grb;*.grib;*.bz2;*.gz;*.grib2;*.grb2)|*.grb;*.grib;*.bz2;*.gz;" +
1687 "*.grib2;*.grb2|" + _("All files") + "(*)|*.*",
1688 wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE, wxDefaultPosition,
1689 wxDefaultSize, _("File Dialog"));
1690
1691 if (dialog->ShowModal() == wxID_OK) {
1692 ::wxBeginBusyCursor();
1693
1694 m_grib_dir = dialog->GetDirectory();
1695 dialog->GetPaths(m_file_names);
1696 OpenFile();
1697 if (g_pi) {
1698 if (g_pi->m_bZoomToCenterAtInit) DoZoomToCenter();
1699 }
1700 SetDialogsStyleSizePosition(true);
1701 }
1702#ifdef __WXOSX__
1703 // Restore to former value.
1704 // Should ideally be done using RAII, but that would require a larger
1705 // refactor.
1706 wxSystemOptions::SetOption(wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES,
1707 saved_show_types_setting);
1708#endif // __WXOSX__
1709 delete dialog;
1710#else
1711 if (!wxDir::Exists(m_grib_dir)) {
1712 wxStandardPathsBase &path = wxStandardPaths::Get();
1713 m_grib_dir = path.GetDocumentsDir();
1714 }
1715
1716 wxString file;
1717 int response = PlatformFileSelectorDialog(
1718 nullptr, &file, _("Select a GRIB file"), m_grib_dir, "", "*.*");
1719
1720 if (response == wxID_OK) {
1721 wxFileName fn(file);
1722 m_grib_dir = fn.GetPath();
1723 m_file_names.Clear();
1724 m_file_names.Add(file);
1725 OpenFile();
1726 SetDialogsStyleSizePosition(true);
1727 }
1728#endif
1729}
1730
1731void GRIBUICtrlBar::CreateActiveFileFromNames(const wxArrayString &filenames) {
1732 if (filenames.GetCount() != 0) {
1733 m_bGRIBActiveFile = nullptr;
1736 }
1737}
1738
1739void GRIBUICtrlBar::PopulateComboDataList() {
1740 int index = 0;
1741 if (m_cRecordForecast->GetCount()) {
1742 index = m_cRecordForecast->GetCurrentSelection();
1743 m_cRecordForecast->Clear();
1744 }
1745
1746 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1747 for (size_t i = 0; i < rsa->GetCount(); i++) {
1748 wxDateTime t(rsa->Item(i).m_Reference_Time);
1749 m_cRecordForecast->Append(toUsrDateTimeFormat_Plugin(t));
1750 }
1751 m_cRecordForecast->SetSelection(index);
1752}
1753
1754void GRIBUICtrlBar::OnZoomToCenterClick(wxCommandEvent &event) {
1755 DoZoomToCenter();
1756#if 0
1757 if(!m_pTimelineSet) return;
1758
1759 double latmin,latmax,lonmin,lonmax;
1760 if(!GetGribZoneLimits(m_pTimelineSet, &latmin, &latmax, &lonmin, &lonmax ))
1761 return;
1762
1763 //::wxBeginBusyCursor();
1764
1765 //calculate overlay size
1766 double width = lonmax - lonmin;
1767 double height = latmax - latmin;
1768
1769 // Calculate overlay center
1770 double clat = latmin + height / 2;
1771 double clon = lonmin + width / 2;
1772
1773 //try to limit the ppm at a reasonable value
1774 if(width > 120.){
1775 lonmin = clon - 60.;
1776 lonmax = clon + 60.;
1777 }
1778 if(height > 120.){
1779 latmin = clat - 60.;
1780 latmax = clat + 60.;
1781 }
1782
1783
1784 //Calculate overlay width & height in nm (around the center)
1785 double ow, oh;
1786 DistanceBearingMercator_Plugin(clat, lonmin, clat, lonmax, nullptr, &ow );
1787 DistanceBearingMercator_Plugin( latmin, clon, latmax, clon, nullptr, &oh );
1788
1789 //calculate screen size
1790 int w = pPlugIn->GetGRIBOverlayFactory()->m_ParentSize.GetWidth();
1791 int h = pPlugIn->GetGRIBOverlayFactory()->m_ParentSize.GetHeight();
1792
1793 //calculate final ppm scale to use
1794 double ppm;
1795 ppm = wxMin(w/(ow*1852), h/(oh*1852)) * ( 100 - fabs( clat ) ) / 90;
1796
1797 ppm = wxMin(ppm, 1.0);
1798
1799 JumpToPosition(clat, clon, ppm);
1800
1801 RequestRefresh( pParent );
1802#endif
1803}
1804
1805void GRIBUICtrlBar::DoZoomToCenter() {
1806 if (!m_pTimelineSet) return;
1807
1808 double latmin, latmax, lonmin, lonmax;
1809 if (!GetGribZoneLimits(m_pTimelineSet, &latmin, &latmax, &lonmin, &lonmax))
1810 return;
1811
1812 //::wxBeginBusyCursor();
1813
1814 // calculate overlay size
1815 double width = lonmax - lonmin;
1816 double height = latmax - latmin;
1817
1818 // Calculate overlay center
1819 double clat = latmin + height / 2;
1820 double clon = lonmin + width / 2;
1821
1822 // try to limit the ppm at a reasonable value
1823 if (width > 120.) {
1824 lonmin = clon - 60.;
1825 lonmax = clon + 60.;
1826 }
1827 if (height > 120.) {
1828 latmin = clat - 60.;
1829 latmax = clat + 60.;
1830 }
1831
1832 // Calculate overlay width & height in nm (around the center)
1833 double ow, oh;
1834 DistanceBearingMercator_Plugin(clat, lonmin, clat, lonmax, nullptr, &ow);
1835 DistanceBearingMercator_Plugin(latmin, clon, latmax, clon, nullptr, &oh);
1836
1837 wxWindow *wx = GetGRIBCanvas();
1838 // calculate screen size
1839 int w = wx->GetSize().x;
1840 int h = wx->GetSize().y;
1841
1842 // calculate final ppm scale to use
1843 double ppm;
1844 ppm = wxMin(w / (ow * 1852), h / (oh * 1852)) * (100 - fabs(clat)) / 90;
1845
1846 ppm = wxMin(ppm, 1.0);
1847
1848 CanvasJumpToPosition(wx, clat, clon, ppm);
1849}
1850
1851void GRIBUICtrlBar::OnPrev(wxCommandEvent &event) {
1852 if (m_tPlayStop.IsRunning())
1853 return; // do nothing when play back is running !
1854
1855 RestaureSelectionString();
1856
1857 int selection;
1858 if (m_pNowMode)
1859 selection = GetNearestIndex(GetNow(), 1);
1860 else if (m_InterpolateMode)
1861 selection =
1862 GetNearestIndex(TimelineTime(), 1); /* set to interpolated entry */
1863 else
1864 selection = m_cRecordForecast->GetCurrentSelection();
1865
1866 m_pNowMode = false;
1867 m_InterpolateMode = false;
1868
1869 m_cRecordForecast->SetSelection(selection < 1 ? 0 : selection - 1);
1870
1871 TimelineChanged();
1872}
1873
1874void GRIBUICtrlBar::OnNext(wxCommandEvent &event) {
1875 if (m_tPlayStop.IsRunning())
1876 return; // do nothing when play back is running !
1877
1878 RestaureSelectionString();
1879
1880 int selection;
1881 if (m_pNowMode)
1882 selection = GetNearestIndex(GetNow(), 2);
1883 else if (m_InterpolateMode)
1884 selection =
1885 GetNearestIndex(TimelineTime(), 2); /* set to interpolated entry */
1886 else
1887 selection = m_cRecordForecast->GetCurrentSelection();
1888
1889 m_cRecordForecast->SetSelection(selection);
1890
1891 m_pNowMode = false;
1892 m_InterpolateMode = false;
1893
1894 if (selection == (int)m_cRecordForecast->GetCount() - 1)
1895 return; // end of list
1896
1897 m_cRecordForecast->SetSelection(selection + 1);
1898
1899 TimelineChanged();
1900}
1901
1902void GRIBUICtrlBar::ComputeBestForecastForNow() {
1904 pPlugIn->GetGRIBOverlayFactory()->SetGribTimelineRecordSet(nullptr);
1905 return;
1906 }
1907
1908 wxDateTime now = GetNow();
1909
1910 if (m_OverlaySettings.m_bInterpolate)
1911 m_sTimeline->SetValue(GetNearestValue(now, 0));
1912 else {
1913 m_cRecordForecast->SetSelection(GetNearestIndex(now, 0));
1914 m_sTimeline->SetValue(m_cRecordForecast->GetCurrentSelection());
1915 }
1916
1917 if (pPlugIn->GetStartOptions() !=
1918 2) { // no interpolation at start : take the nearest forecast
1919 m_InterpolateMode = m_OverlaySettings.m_bInterpolate;
1920 TimelineChanged();
1921 return;
1922 }
1923 // interpolation on 'now' at start
1924 m_InterpolateMode = true;
1925 m_pNowMode = true;
1926 SetGribTimelineRecordSet(
1927 GetTimeLineRecordSet(now)); // take current time & interpolate forecast
1928
1929 RestaureSelectionString(); // eventually restaure the previousely saved
1930 // wxChoice date time label
1931 m_cRecordForecast->SetSelection(GetNearestIndex(now, 2));
1932 SaveSelectionString(); // memorize the new selected wxChoice date time label
1933 wxString nowTime = toUsrDateTimeFormat_Plugin(now);
1934 m_cRecordForecast->SetString(m_Selection_index,
1935 nowTime); // write the now date time label
1936 // in the right place in wxChoice
1937 m_cRecordForecast->SetStringSelection(nowTime); // put it in the box
1938
1940
1941 pPlugIn->SendTimelineMessage(now);
1942 RequestRefresh(GetGRIBCanvas());
1943}
1944
1945void GRIBUICtrlBar::SetGribTimelineRecordSet(
1946 GribTimelineRecordSet *pTimelineSet) {
1947 delete m_pTimelineSet;
1948 m_pTimelineSet = pTimelineSet;
1949
1950 if (!pPlugIn->GetGRIBOverlayFactory()) return;
1951
1952 pPlugIn->GetGRIBOverlayFactory()->SetGribTimelineRecordSet(m_pTimelineSet);
1953}
1954
1955void GRIBUICtrlBar::SetTimeLineMax(bool SetValue) {
1956 int oldmax = wxMax(m_sTimeline->GetMax(), 1),
1957 oldval = m_sTimeline->GetValue(); // memorize the old range and value
1958
1959 if (m_OverlaySettings.m_bInterpolate) {
1960 int stepmin =
1961 m_OverlaySettings.GetMinFromIndex(m_OverlaySettings.m_SlicesPerUpdate);
1962 m_sTimeline->SetMax(m_TimeLineHours * 60 / stepmin);
1963 } else {
1965 ArrayOfGribRecordSets *rsa = m_bGRIBActiveFile->GetRecordSetArrayPtr();
1966 m_sTimeline->SetMax(rsa->GetCount() - 1);
1967 }
1968 }
1969 // try to retrieve a coherent timeline value with the new timeline range if it
1970 // has changed
1971 if (SetValue && m_sTimeline->GetMax() != 0) {
1972 if (m_pNowMode)
1973 ComputeBestForecastForNow();
1974 else
1975 m_sTimeline->SetValue(m_sTimeline->GetMax() * oldval / oldmax);
1976 }
1977}
1978
1979void GRIBUICtrlBar::SetFactoryOptions() {
1980 if (m_pTimelineSet) m_pTimelineSet->ClearCachedData();
1981
1982 pPlugIn->GetGRIBOverlayFactory()->ClearCachedData();
1983
1985 RequestRefresh(GetGRIBCanvas());
1986}
1987
1988void GRIBUICtrlBar::OnFormatRefreshTimer(wxTimerEvent &event) {
1989 // Check if time format has changed by comparing current format with saved
1990 // format
1991 wxDateTime referenceDate(1, wxDateTime::Jan, 2021, 12, 0, 0);
1992 wxString currentFormat = toUsrDateTimeFormat_Plugin(referenceDate);
1993
1994 if (currentFormat != m_sLastTimeFormat) {
1995 // Time format has changed, update all time displays
1996 m_sLastTimeFormat = currentFormat;
1997
1999 // Refresh the time format in the dropdown list
2000 PopulateComboDataList();
2001
2002 // Update the timeline display
2003 TimelineChanged();
2004
2005 // Request a refresh to update any on-screen displays
2006 RequestRefresh(GetGRIBCanvas());
2007 }
2008 }
2009}
2010
2011//----------------------------------------------------------------------------------------------------------
2012// GRIBFile Object Implementation
2013//----------------------------------------------------------------------------------------------------------
2014unsigned int GRIBFile::ID = 0;
2015
2016GRIBFile::GRIBFile(const wxArrayString &file_names, bool CumRec, bool WaveRec,
2017 bool newestFile)
2018 : m_counter(++ID) {
2019 m_bOK = false; // Assume ok until proven otherwise
2020 m_pGribReader = nullptr;
2021 m_last_message = wxEmptyString;
2022 for (unsigned int i = 0; i < file_names.GetCount(); i++) {
2023 wxString file_name = file_names[i];
2024 if (::wxFileExists(file_name)) m_bOK = true;
2025 }
2026
2027 if (m_bOK == false) {
2028 m_last_message = _(" files don't exist!");
2029 return;
2030 }
2031 // Use the zyGrib support classes, as (slightly) modified locally....
2032 m_pGribReader = new GribReader();
2033
2034 // Read and ingest the entire GRIB file.......
2035 m_bOK = false;
2036 wxString file_name;
2037 for (unsigned int i = 0; i < file_names.GetCount(); i++) {
2038 file_name = file_names[i];
2039 m_pGribReader->OpenFile(file_name);
2040
2041 if (m_pGribReader->IsOk()) {
2042 m_bOK = true;
2043 if (newestFile) {
2044 break;
2045 }
2046 }
2047 }
2048 if (m_bOK == false) {
2049 m_last_message = _(" can't be read!");
2050 return;
2051 }
2052
2053 if (newestFile) {
2054 m_FileNames.Clear();
2055 m_FileNames.Add(file_name);
2056 } else {
2057 m_FileNames = file_names;
2058 }
2059
2060 // fixup Accumulation records
2061 m_pGribReader->ComputeAccumulationRecords(GRB_PRECIP_TOT, LV_GND_SURF, 0);
2062 m_pGribReader->ComputeAccumulationRecords(GRB_PRECIP_RATE, LV_GND_SURF, 0);
2063 m_pGribReader->ComputeAccumulationRecords(GRB_CLOUD_TOT, LV_ATMOS_ALL, 0);
2064
2065 if (CumRec)
2066 m_pGribReader->CopyFirstCumulativeRecord(); // add missing records if
2067 // option selected
2068 if (WaveRec)
2069 m_pGribReader->CopyMissingWaveRecords(); // "" ""
2070
2071 m_nGribRecords = m_pGribReader->GetTotalNumberOfGribRecords();
2072
2073 // Walk the GribReader date list to populate our array of GribRecordSets
2074
2075 std::set<time_t>::iterator iter;
2076 std::set<time_t> date_list = m_pGribReader->GetListDates();
2077 for (iter = date_list.begin(); iter != date_list.end(); iter++) {
2078 GribRecordSet *t = new GribRecordSet(m_counter);
2079 time_t reftime = *iter;
2080 t->m_Reference_Time = reftime;
2081 m_GribRecordSetArray.Add(t);
2082 }
2083
2084 // Convert from zyGrib organization by data type/level to our organization
2085 // by time.
2086
2087 GribRecord *pRec;
2088 bool isOK(false);
2089 bool polarWind(false);
2090 bool polarCurrent(false);
2091 bool sigWave(false);
2092 bool sigH(false);
2093 // Get the map of GribRecord vectors
2094 std::map<std::string, std::vector<GribRecord *> *> *p_map =
2095 m_pGribReader->GetGribMap();
2096
2097 // Iterate over the map to get vectors of related GribRecords
2098 std::map<std::string, std::vector<GribRecord *> *>::iterator it;
2099 for (it = p_map->begin(); it != p_map->end(); it++) {
2100 std::vector<GribRecord *> *ls = (*it).second;
2101 for (zuint i = 0; i < ls->size(); i++) {
2102 pRec = ls->at(i);
2103 isOK = true;
2104 time_t thistime = pRec->GetRecordCurrentDate();
2105
2106 // Search the GribRecordSet array for a GribRecordSet with matching time
2107 for (unsigned int j = 0; j < m_GribRecordSetArray.GetCount(); j++) {
2108 if (m_GribRecordSetArray.Item(j).m_Reference_Time == thistime) {
2109 int idx = -1, mdx = -1;
2110 switch (pRec->GetDataType()) {
2111 case GRB_WIND_DIR:
2112 polarWind = true;
2113 // fall through
2114 case GRB_WIND_VX:
2115 if (pRec->GetLevelType() == LV_ISOBARIC) {
2116 switch (pRec->GetLevelValue()) {
2117 case 300:
2118 idx = Idx_WIND_VX300;
2119 break;
2120 case 500:
2121 idx = Idx_WIND_VX500;
2122 break;
2123 case 700:
2124 idx = Idx_WIND_VX700;
2125 break;
2126 case 850:
2127 idx = Idx_WIND_VX850;
2128 break;
2129 }
2130 } else
2131 idx = Idx_WIND_VX;
2132 break;
2133 case GRB_WIND_SPEED:
2134 polarWind = true;
2135 // fall through
2136 case GRB_WIND_VY:
2137 if (pRec->GetLevelType() == LV_ISOBARIC) {
2138 switch (pRec->GetLevelValue()) {
2139 case 300:
2140 idx = Idx_WIND_VY300;
2141 break;
2142 case 500:
2143 idx = Idx_WIND_VY500;
2144 break;
2145 case 700:
2146 idx = Idx_WIND_VY700;
2147 break;
2148 case 850:
2149 idx = Idx_WIND_VY850;
2150 break;
2151 }
2152 } else
2153 idx = Idx_WIND_VY;
2154 break;
2155 case GRB_CUR_DIR:
2156 polarCurrent = true;
2157 // fall through
2158 case GRB_UOGRD:
2159 idx = Idx_SEACURRENT_VX;
2160 break;
2161 case GRB_CUR_SPEED:
2162 polarCurrent = true;
2163 // fall through
2164 case GRB_VOGRD:
2165 idx = Idx_SEACURRENT_VY;
2166 break;
2167 case GRB_WIND_GUST:
2168 idx = Idx_WIND_GUST;
2169 break;
2170 case GRB_PRESSURE:
2171 idx = Idx_PRESSURE;
2172 break;
2173 case GRB_HTSGW:
2174 sigH = true;
2175 idx = Idx_HTSIGW;
2176 break;
2177 case GRB_PER:
2178 sigWave = true;
2179 idx = Idx_WVPER;
2180 break;
2181 case GRB_DIR:
2182 sigWave = true;
2183 idx = Idx_WVDIR;
2184 break;
2185 case GRB_WVHGT:
2186 idx = Idx_HTSIGW;
2187 break; // Translation from NOAA WW3
2188 case GRB_WVPER:
2189 idx = Idx_WVPER;
2190 break;
2191 case GRB_WVDIR:
2192 idx = Idx_WVDIR;
2193 break;
2194 case GRB_PRECIP_RATE:
2195 case GRB_PRECIP_TOT:
2196 idx = Idx_PRECIP_TOT;
2197 break;
2198 case GRB_CLOUD_TOT:
2199 idx = Idx_CLOUD_TOT;
2200 break;
2201 case GRB_TEMP:
2202 if (pRec->GetLevelType() == LV_ISOBARIC) {
2203 switch (pRec->GetLevelValue()) {
2204 case 300:
2205 idx = Idx_AIR_TEMP300;
2206 break;
2207 case 500:
2208 idx = Idx_AIR_TEMP500;
2209 break;
2210 case 700:
2211 idx = Idx_AIR_TEMP700;
2212 break;
2213 case 850:
2214 idx = Idx_AIR_TEMP850;
2215 break;
2216 }
2217 } else
2218 idx = Idx_AIR_TEMP;
2219 if (pRec->GetDataCenterModel() == NORWAY_METNO)
2220 mdx = 1000 + NORWAY_METNO;
2221 break;
2222 case GRB_WTMP:
2223 idx = Idx_SEA_TEMP;
2224 if (pRec->GetDataCenterModel() == NOAA_GFS) mdx = 1000 + NOAA_GFS;
2225 break;
2226 case GRB_CAPE:
2227 idx = Idx_CAPE;
2228 break;
2229 case GRB_COMP_REFL:
2230 idx = Idx_COMP_REFL;
2231 break;
2232 case GRB_HUMID_REL:
2233 if (pRec->GetLevelType() == LV_ISOBARIC) {
2234 switch (pRec->GetLevelValue()) {
2235 case 300:
2236 idx = Idx_HUMID_RE300;
2237 break;
2238 case 500:
2239 idx = Idx_HUMID_RE500;
2240 break;
2241 case 700:
2242 idx = Idx_HUMID_RE700;
2243 break;
2244 case 850:
2245 idx = Idx_HUMID_RE850;
2246 break;
2247 }
2248 }
2249 break;
2250 case GRB_GEOPOT_HGT:
2251 if (pRec->GetLevelType() == LV_ISOBARIC) {
2252 switch (pRec->GetLevelValue()) {
2253 case 300:
2254 idx = Idx_GEOP_HGT300;
2255 break;
2256 case 500:
2257 idx = Idx_GEOP_HGT500;
2258 break;
2259 case 700:
2260 idx = Idx_GEOP_HGT700;
2261 break;
2262 case 850:
2263 idx = Idx_GEOP_HGT850;
2264 break;
2265 }
2266 }
2267 break;
2268 }
2269 if (idx == -1) {
2270 // XXX bug ?
2271 break;
2272 }
2273
2274 bool skip = false;
2275
2276 if (m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[idx]) {
2277 // already one
2278 GribRecord *oRec =
2279 m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[idx];
2280 if (idx == Idx_PRESSURE) {
2281 skip = (oRec->GetLevelType() == LV_MSL);
2282 } else {
2283 // we favor UV over DIR/SPEED
2284 if (polarWind) {
2285 if (oRec->GetDataType() == GRB_WIND_VY ||
2286 oRec->GetDataType() == GRB_WIND_VX)
2287 skip = true;
2288 }
2289 if (polarCurrent) {
2290 if (oRec->GetDataType() == GRB_UOGRD ||
2291 oRec->GetDataType() == GRB_VOGRD)
2292 skip = true;
2293 }
2294 // favor average aka time_range == 3 (HRRR subhourly subsets have
2295 // both 3 and 0 records for winds)
2296 if (!skip && (oRec->GetTimeRange() == 3)) {
2297 skip = true;
2298 }
2299 // we favor significant Wave other wind wave.
2300 if (sigH) {
2301 if (oRec->GetDataType() == GRB_HTSGW) skip = true;
2302 }
2303 if (sigWave) {
2304 if (oRec->GetDataType() == GRB_DIR ||
2305 oRec->GetDataType() == GRB_PER)
2306 skip = true;
2307 }
2308 }
2309 }
2310 if (!skip) {
2311 m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[idx] = pRec;
2312 if (m_GribIdxArray.Index(idx) == wxNOT_FOUND)
2313 m_GribIdxArray.Add(idx, 1);
2314 if (mdx != -1 && m_GribIdxArray.Index(mdx) == wxNOT_FOUND)
2315 m_GribIdxArray.Add(mdx, 1);
2316 }
2317 break;
2318 }
2319 }
2320 }
2321 }
2322
2323 if (polarWind || polarCurrent) {
2324 for (unsigned int j = 0; j < m_GribRecordSetArray.GetCount(); j++) {
2325 for (unsigned int i = 0; i < Idx_COUNT; i++) {
2326 int idx = -1;
2327 if (polarWind) {
2328 GribRecord *pRec =
2329 m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[i];
2330
2331 if (pRec != nullptr && pRec->GetDataType() == GRB_WIND_DIR) {
2332 switch (i) {
2333 case Idx_WIND_VX300:
2334 idx = Idx_WIND_VY300;
2335 break;
2336 case Idx_WIND_VX500:
2337 idx = Idx_WIND_VY500;
2338 break;
2339 case Idx_WIND_VX700:
2340 idx = Idx_WIND_VY700;
2341 break;
2342 case Idx_WIND_VX850:
2343 idx = Idx_WIND_VY850;
2344 break;
2345 case Idx_WIND_VX:
2346 idx = Idx_WIND_VY;
2347 break;
2348 default:
2349 break;
2350 }
2351 if (idx != -1) {
2352 GribRecord *pRec1 =
2353 m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[idx];
2354 if (pRec1 != nullptr && pRec1->GetDataType() == GRB_WIND_SPEED)
2355 GribRecord::Polar2UV(pRec, pRec1);
2356 }
2357 }
2358 }
2359 if (polarCurrent) {
2360 idx = -1;
2361 GribRecord *pRec =
2362 m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[i];
2363
2364 if (pRec != nullptr && pRec->GetDataType() == GRB_CUR_DIR) {
2365 switch (i) {
2366 case Idx_SEACURRENT_VX:
2367 idx = Idx_SEACURRENT_VY;
2368 break;
2369 default:
2370 break;
2371 }
2372 if (idx != -1) {
2373 GribRecord *pRec1 =
2374 m_GribRecordSetArray.Item(j).m_GribRecordPtrArray[idx];
2375 if (pRec1 != nullptr && pRec1->GetDataType() == GRB_CUR_SPEED)
2376 GribRecord::Polar2UV(pRec, pRec1);
2377 }
2378 }
2379 }
2380 }
2381 }
2382 }
2383
2384 if (isOK)
2385 m_pRefDateTime =
2386 pRec->GetRecordRefDate(); // to ovoid crash with some bad files
2387}
2388
2389GRIBFile::~GRIBFile() { delete m_pGribReader; }
2390
2391//---------------------------------------------------------------------------------------
2392// GRIB Cursor Data Ctrl & Display implementation
2393//---------------------------------------------------------------------------------------
2394GRIBUICData::GRIBUICData(GRIBUICtrlBar &parent)
2395#ifdef __WXOSX__
2396 : GRIBUICDataBase(parent.pParent, CURSOR_DATA, _("GRIB Display Control"),
2397 wxDefaultPosition, wxDefaultSize,
2398 wxSYSTEM_MENU | wxNO_BORDER | wxSTAY_ON_TOP)
2399#else
2400 : GRIBUICDataBase(&parent, CURSOR_DATA, _("GRIB Display Control"),
2401 wxDefaultPosition, wxDefaultSize,
2402 wxSYSTEM_MENU | wxNO_BORDER)
2403#endif
2404 ,
2405 m_gpparent(parent) {
2406 // m_gGrabber = new GribGrabberWin( this );
2407 // fgSizer58->Add( m_gGrabber, 0, wxALL, 0 );
2408
2409 m_gCursorData = new CursorData(this, m_gpparent);
2410 m_fgCdataSizer->Add(m_gCursorData, 0, wxALL, 0);
2411
2412 Connect(wxEVT_MOVE, wxMoveEventHandler(GRIBUICData::OnMove));
2413}
2414
2415void GRIBUICData::OnMove(wxMoveEvent &event) {
2416 int w, h;
2417 GetScreenPosition(&w, &h);
2418 m_gpparent.pPlugIn->SetCursorDataXY(wxPoint(w, h));
2419}
2420
2421//---------------------------------------------------------------------------------------
2422// Android Utility Methods
2423//---------------------------------------------------------------------------------------
2424#ifdef __ANDROID__
2425
2426#include <QtAndroidExtras/QAndroidJniObject>
2427
2428bool CheckPendingJNIException() {
2429 if (!java_vm) {
2430 // qDebug() << "java_vm is nullptr.";
2431 return true;
2432 }
2433
2434 JNIEnv *jenv;
2435 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2436 // qDebug() << "GetEnv failed.";
2437 return true;
2438 }
2439
2440 if ((jenv)->ExceptionCheck() == JNI_TRUE) {
2441 // qDebug() << "Found JNI Exception Pending.";
2442 return true;
2443 }
2444
2445 return false;
2446}
2447
2448wxString callActivityMethod_ss(const char *method, wxString parm) {
2449 if (!java_vm) {
2450 // qDebug() << "java_vm is nullptr.";
2451 return "NOK";
2452 }
2453
2454 if (CheckPendingJNIException()) return "NOK";
2455
2456 wxString return_string;
2457 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2458 "org/qtproject/qt5/android/QtNative", "activity",
2459 "()Landroid/app/Activity;");
2460 if (CheckPendingJNIException()) return "NOK";
2461
2462 if (!activity.isValid()) {
2463 // qDebug() << "Activity is not valid";
2464 return return_string;
2465 }
2466
2467 // Need a Java environment to decode the resulting string
2468 JNIEnv *jenv;
2469 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2470 // qDebug() << "GetEnv failed.";
2471 return "jenv Error";
2472 }
2473
2474 jstring p = (jenv)->NewStringUTF(parm.c_str());
2475
2476 // Call the desired method
2477 // qDebug() << "Calling method_ss";
2478 // qDebug() << method;
2479
2480 QAndroidJniObject data = activity.callObjectMethod(
2481 method, "(Ljava/lang/String;)Ljava/lang/String;", p);
2482 if (CheckPendingJNIException()) return "NOK";
2483
2484 // qDebug() << "Back from method_ss";
2485
2486 jstring s = data.object<jstring>();
2487
2488 if ((jenv)->GetStringLength(s)) {
2489 const char *ret_string = (jenv)->GetStringUTFChars(s, nullptr);
2490 return_string = wxString(ret_string, wxConvUTF8);
2491 }
2492
2493 return return_string;
2494}
2495
2496#endif
Tracks and displays GRIB meteorological data at cursor position.
Definition cursor_data.h:52
Manages multiple GRIB record sets from one or more GRIB files.
bool IsOK() const
Checks if file loading and parsing was successful.
wxArrayString & GetFileNames()
Gets the list of source filenames being used.
GRIBFile(const wxArrayString &file_names, bool CumRec, bool WaveRec, bool newestFile=false)
Creates a new GRIBFile by parsing one or more GRIB files.
ArrayOfGribRecordSets * GetRecordSetArrayPtr()
Gets pointer to array of record sets organized by timestamp.
time_t GetRefDateTime() const
Returns the reference datetime of the GRIB data, as the number of seconds since the epoch.
Dialog showing GRIB data in a table format.
Definition grib_table.h:70
void SetTableSizePosition(int vpWidth, int vpHeight)
Set the table size and position relative to viewport.
void InitGribTable(ArrayOfGribRecordSets *rsa)
Initialize the GRIB data table.
Class GRIBUICDataBase.
Class GRIBUICtrlBarBase.
void SetViewPortWithFocus(PlugIn_ViewPort *vp)
Set the ViewPort that has the focus.
void GetProjectedLatLon(int &x, int &y, PlugIn_ViewPort *vp)
Gets the projected position of vessel based on current course, speed and forecast time.
GribOverlaySettings m_OverlaySettings
Settings that control how GRIB data is displayed and overlaid.
void SetRequestButtonBitmap(int type)
Set the icon and tooltip for the download request button.
wxTimer m_tPlayStop
Timer for controlling GRIB animation playback.
void SetViewPortUnderMouse(PlugIn_ViewPort *vp)
Set the ViewPort under the mouse.
GribPi * pPlugIn
Plugin instance that owns this control bar.
wxString m_grib_dir
Directory containing GRIB files.
GRIBFile * m_bGRIBActiveFile
Currently active GRIB file being displayed.
wxDateTime TimelineTime()
Returns the selected time in the GRIB timeline widget.
void UpdateTrackingControl()
Schedules an update of the GRIB data values display at current cursor position.
GribTimelineRecordSet * m_pTimelineSet
Current set of GRIB records for timeline playback.
void OnRequestForecastData(wxCommandEvent &event) override
Callback invoked when user clicks download/request forecast data.
wxArrayString m_file_names
List of GRIB filenames being displayed.
GribTimelineRecordSet * GetTimeLineRecordSet(wxDateTime time)
Retrieves or creates a temporally interpolated GRIB record set for a specific timestamp.
bool GetCopyFirstCumRec() const
Returns true if cumulative parameters like precipitation and cloud cover should initialize their star...
Definition grib_pi.h:152
bool GetCopyMissWaveRec() const
Returns true if wave data should be propagated across time periods where wave records are missing.
Definition grib_pi.h:160
void CopyMissingWaveRecords()
Fills gaps in wave-related data fields by propagating known values across missing time periods.
void CopyFirstCumulativeRecord()
Initializes cumulative meteorological parameters by copying their first record values.
Manages a collection of GribRecord objects representing multiple meteorological parameters at a singl...
void SetUnRefGribRecord(int i, GribRecord *pGR)
Sets a GRIB record that this set owns and will be responsible for deleting.
GribRecord * m_GribRecordPtrArray[Idx_COUNT]
Array of pointers to GRIB records representing different meteorological parameters.
time_t m_Reference_Time
Reference time for this set of records, as the number of seconds since the epoch.
A meteorological data grid from a GRIB (Gridded Binary) file.
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.
static void Polar2UV(GribRecord *pDIR, GribRecord *pSPEED)
Converts wind or current values from polar (direction/speed) to cartesian (U/V) components.
zuchar GetTimeRange() const
Returns the time range indicator that defines how P1 and P2 should be interpreted.
zuint GetDataCenterModel() const
Returns the numerical weather prediction model/center that produced this data.
static GribRecord * Interpolated2DRecord(GribRecord *&rety, const GribRecord &rec1x, const GribRecord &rec1y, const GribRecord &rec2x, const GribRecord &rec2y, double d)
Creates temporally interpolated records for vector fields (wind, currents).
zuchar GetDataType() const
Returns the type of meteorological parameter stored in this grid.
static GribRecord * InterpolatedRecord(const GribRecord &rec1, const GribRecord &rec2, double d, bool dir=false)
Creates a new GribRecord by temporally interpolating between two time points.
double GetInterpolatedValue(double px, double py, bool numericalInterpolation=true, bool dir=false) const
Get spatially interpolated value at exact lat/lon position.
zuint GetLevelValue() const
Returns the numeric value associated with the level type.
zuchar GetLevelType() const
Returns the type of vertical level for this grid's data.
Manages GRIB file request configuration and downloads.
void OnVpUnderMouseChange(PlugIn_ViewPort *vp)
Callback invoked when the view port under mouse has changed.
void OnVpWithFocusChange(PlugIn_ViewPort *vp)
Callback invoked when the focused view port has changed, such as in multi-chart mode when user switch...
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).
GribTimelineRecordSet(unsigned int cnt)
Creates a timeline record set containing temporally interpolated GRIB records.
Contains view parameters and status information for a chart display viewport.
int pix_width
Viewport width in pixels.
double lon_max
Maximum longitude of the viewport.
double lat_max
Maximum latitude of the viewport.
int pix_height
Viewport height in pixels.
double lon_min
Minimum longitude of the viewport.
double lat_min
Minimum latitude of the viewport.
The JSON parser.
Definition jsonreader.h:45
int Parse(const wxString &doc, wxJSONValue *val)
Parse the JSON document.
The JSON value class implementation.
Definition jsonval.h:79
The JSON document writer.
Definition jsonwriter.h:45
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Email Request System for GRIB Data.
GRIB Weather Data Plugin for OpenCPN.
@ Idx_AIR_TEMP850
Air temperature at 850 hPa in Kelvin (K)
@ 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_HUMID_RE850
Relative humidity at 850 hPa in % (percent, range 0-100%)
@ 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_AIR_TEMP300
Air temperature at 300 hPa in Kelvin (K)
@ Idx_COUNT
Number of supported GRIB record types.
@ Idx_WIND_VY850
Wind velocity Y component at 850 hPa in m/s.
@ Idx_HUMID_RE500
Relative humidity at 500 hPa in % (percent, range 0-100%)
@ Idx_WIND_VX300
Wind velocity X component at 300 hPa in m/s.
@ Idx_HUMID_RE300
Relative humidity at 300 hPa in % (percent, range 0-100%)
@ Idx_WIND_VX850
Wind velocity X component at 850 hPa in m/s.
@ Idx_WIND_VY300
Wind velocity Y component at 300 hPa in m/s.
@ Idx_WVPER
Wave period.
@ Idx_WIND_VX700
Wind velocity X component at 700 hPa in m/s.
@ Idx_HTSIGW
Significant wave height in meters.
@ Idx_AIR_TEMP700
Air temperature at 700 hPa in Kelvin (K)
@ Idx_WIND_VY500
Wind velocity Y component at 500 hPa in m/s.
@ Idx_GEOP_HGT500
Geopotential height at 500 hPa in gpm (geopotential meters)
@ Idx_SEACURRENT_VY
Sea current velocity Y component in m/s.
@ Idx_WIND_VX500
Wind velocity X component at 500 hPa in m/s.
@ Idx_GEOP_HGT300
Geopotential height at 300 hPa in gpm (geopotential meters)
@ Idx_GEOP_HGT700
Geopotential height at 700 hPa in gpm (geopotential meters)
@ Idx_WIND_VY700
Wind velocity Y component at 700 hPa in m/s.
@ Idx_SEA_TEMP
Sea surface temperature in Kelvin (K)
@ Idx_WIND_VY
Surface wind velocity Y component in m/s.
@ Idx_GEOP_HGT850
Geopotential height at 850 hPa in gpm (geopotential meters)
@ Idx_SEACURRENT_VX
Sea current velocity X component in m/s.
@ Idx_AIR_TEMP500
Air temperature at 500 hPa in Kelvin (K)
@ Idx_HUMID_RE700
Relative humidity at 700 hPa in % (percent, range 0-100%)
@ Idx_CAPE
Convective Available Potential Energy in J/kg (Joules per kilogram)
int m_SavedZoneSelMode
Persisted version of the GRIB area selection mode.
int m_ZoneSelMode
Tracks the current state of GRIB area selection for zone coordinates.
GRIB Data Table View and Export Interface.
int m_SavedZoneSelMode
Persisted version of the GRIB area selection mode.
int m_ZoneSelMode
Tracks the current state of GRIB area selection for zone coordinates.
GRIB Weather Data Control Interface.
@ AUTO_SELECTION
Area automatically set from current viewport bounds.
Definition grib_ui_dlg.h:87
@ DRAW_SELECTION
Manual mode has been selected.
@ START_SELECTION
User has clicked Shift + Left click and is drawing the bounding box by dragging the mouse.
Definition grib_ui_dlg.h:93
@ SAVED_SELECTION
Area loaded from previously saved coordinates.
Definition grib_ui_dlg.h:88
#define ID_BTNREQUEST
ID of button for requesting/downloading GRIB data./*#end#*‍/.
#define wxCLOSE_BOX
Definition mark_info.h:133
PlugIn Object Definition/API.
wxWindow * GetOCPNCanvasWindow()
Gets OpenCPN's main canvas window.
void SetCanvasContextMenuItemViz(int item, bool viz)
Temporarily changes context menu item visibility.
int GetCanvasCount()
Gets total number of chart canvases.
wxFileConfig * GetOCPNConfigObject()
Gets OpenCPN's configuration object.
wxFont * FindOrCreateFont_PlugIn(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline, const wxString &facename, wxFontEncoding encoding)
Creates or finds a font in the font cache.
void PositionBearingDistanceMercator_Plugin(double lat, double lon, double brg, double dist, double *dlat, double *dlon)
Calculates destination point given starting point, bearing and distance.
void JumpToPosition(double lat, double lon, double scale)
Centers chart display on specified position at given scale.
int PlatformFileSelectorDialog(wxWindow *parent, wxString *file_spec, wxString Title, wxString initDir, wxString suggestedName, wxString wildcard)
Shows platform-optimized file selector dialog.
wxRect GetMasterToolbarRect()
Gets bounding rectangle of master toolbar.
void CanvasJumpToPosition(wxWindow *canvas, double lat, double lon, double scale)
Centers specified canvas on given position at given scale.
void GetCanvasPixLL(PlugIn_ViewPort *vp, wxPoint *pp, double lat, double lon)
Converts lat/lon to canvas physical pixel coordinates.
void DimeWindow(wxWindow *win)
Applies system color scheme to window.
wxWindow * GetCanvasByIndex(int canvasIndex)
Gets chart canvas window by index.
void RequestRefresh(wxWindow *win)
Requests window refresh.
void DistanceBearingMercator_Plugin(double lat0, double lon0, double lat1, double lon1, double *brg, double *dist)
Calculates bearing and distance between two points using Mercator projection.
OpenGL Platform Abstraction Layer.
wxString toUsrDateTimeFormat_Plugin(const wxDateTime date_time, const DateTimeFormatOptions &options)
Format a date/time to a localized string representation, conforming to the global date/time format an...