-
Notifications
You must be signed in to change notification settings - Fork 0
/
xqct.cpp
1386 lines (1221 loc) · 45.4 KB
/
xqct.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* > xqct.cpp
* 1.01 arb Fri Jul 30 19:05:09 BST 2010 - Finished nice version.
* 1.00 arb Sat Jul 10 19:47:16 BST 2010 - Finished most basic version.
* 0.01 arb Mon May 17 10:20:36 BST 2010
*/
static const char SCCSid[] = "@(#)xqct.cpp 1.01 (C) 2010 arb QuickChart display";
/*
* Displays a georeferenced QuickChart with tidal information superimposed.
*/
/*
* Configuration:
* Define RESTRICT_TIDAL_DIAMONDS_TO_SELECTED_CHART if you want to
* only load tidal diamonds which are defined to be on the loaded chart.
* Sometimes larger scale charts have diamonds for the same area which
* it can be useful to load and compare but normally they are worse so
* you may want to define this to get rid of them.
* Define RESTRICT_TIDAL_LEVELS_TO_SELECTED_CHART similarly.
* Define ARROW_SCALE to make them visible.
* Define DEFAULT_CHART as first chart to load
* Define DEFAULT_TIDE_DATA_DIR as directory containing .C1 and .T1 files
* Define DEFAULT_HARMONICS_FILE as
*/
#define DEFAULT_TL_MUST_BE_ON_CHART false // could be on other charts
#define DEFAULT_TL_MUST_BE_UNIQUE true // XXX should be true after debugged
#define DEFAULT_TS_MUST_BE_ON_CHART false // could be on other charts
#define DEFAULT_TS_MUST_BE_UNIQUE true // XXX should be true after debugged
#define DEFAULT_TS_MUST_HAVE_KNOWN_REF true // pointless if TS ref station is unknown
#define ARROW_SCALE 40
#define DEFAULT_CHART "BA1481_1.qct"
//#define DEFAULT_CHART_DIR "/users/local/arb/h/memorymap/v-g"
//#define DEFAULT_CHART_DIR "/mnt/hgfs/VM/tide/maptech"
#define DEFAULT_CHART_DIR qApp->applicationDirPath()+DIRSEPSTR+"qct"
#define DEFAULT_TIDE_DATA_DIR qApp->applicationDirPath()
#define DEFAULT_TIDEC1_ZIP "tidec1.zip"
#define DEFAULT_TIDET1_ZIP "tidet1.zip"
#define DEFAULT_TIDE_ZIP_PASSWORD QString::null
//#define DEFAULT_HARMONICS_FILE "harmonics-dwf-20091227-nonfree.tcd"
#define DEFAULT_HARMONICS_FILE "tcd/world.tcd"
#define DEFAULT_ZOOM_OUT_LEVEL 1 // full size
#define PRINTER_MARGIN_CM 1 // 1 cm margins
#define TIDE_MAX_LINE_LEN 512 // typically 450 bytes max
/*
* Bugs:
* Still some jumps/sticky bits in bearing when new HW time is used,
* maybe caused by tables which don't wrap properly;
* need to think about wraparound or extrapolation rather than clipping.
* WARNING asking for tide at offset 968871.120000 hrs probably caused by
* the simple-minded search for high tide rather than calculating explicitly.
* Speed up TCD using a QDict cache of n,lat,lon rather than a prev String.
* Speed up moon phase - if jtime within synodic month of last jtime.
* Default zoom level in prefs is fairly useless.
* refAtHW is not used yet (it all breaks if value is false).
*/
/*
* To do:
* Date editing: remember last used date or today's date for next time,
* button for today, buttons for prev/next day/week
* Slider at bottom for day?
* Printing:
* Print at full resolution
* Map display:
* Load tiles as needed instead of all at once
* Scale down uses colour interpolation vertically as well as horiz
* Zoom in/out should reload just image data not whole QCT file
* (then wouldn't have to reload tide data either)
* Same code could be used by printing to get full resolution.
* Nice to have:
* Show moon phase in status bar
* Overlays, eg.
* positions of wrecks,
* boat launch locations,
* Zoom should reposition based on mouse position not map center
* Collect all output (errors etc) into a log window
* Is a graph of tide height or current over time useful?
*/
#include <qaccel.h>
#include <qapplication.h>
#include <qcolordialog.h>
#include <qdragobject.h>
#include <qfile.h>
#include <qfiledialog.h>
#include <qgrid.h>
#include <qinputdialog.h>
#include <qkeycode.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qmenubar.h>
#include <qmessagebox.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qpopupmenu.h>
#include <qpaintdevicemetrics.h> // for printing
#include <qprinter.h> // for printing
#include <qregexp.h>
#include <qsettings.h>
#include <qslider.h>
#include <qstatusbar.h>
#include <qtimer.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qvbox.h>
#include <qwhatsthis.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "satlib/dundee.h"
#include "satqt/qapp.h" // for UNICODE_ definitions
#include "satqt/helpdialog.h"
#include "satqt/mrumenu.h"
#include "satqt/inputdialog.h"
#include "satqt/qzip.h"
#include "satqt/calendardialog.h"
#include "satqt/fileopen.xpm"
#include "satqt/filesave.xpm"
#include "satqt/fileprint.xpm"
#include "satqt/calendaricon3.xpm"
#include "osmap/qct.h"
#include "satqt/kconfig.h" // for KAutoConfig
#include "satqt/kautoconfigdialog.h" // for KAutoConfig
#include "configdialog.h" // ditto
#include "qctimage.h"
#include "qctcollection.h"
#include "tidedata.h"
#include "tidecalc.h"
#include "xqct.h"
#define UNUSED(x) ((x)=(x)) /* keep compiler quiet */
static const char * progname = "XQCT";
static const char *LOG_E = "error";
static const char *LOG_I = "xqct";
/* ----------------------------------------------------------------------------
* For checking whether two tidal locations are close enough that they would
* overlap or in fact are identical duplicates and so only one is necessary.
* XXX should get pixel resolution in degrees from QCT and return closeEnough
* if with so many pixels rather than so many degrees.
*/
static double
distanceBetween(double lat0, double lon0, double lat1, double lon1)
{
// Should calculate Great Circle distance, but OK for close points
// Don't bother to take the square root for speed.
//debugf(1,"dist %f->%f and %f->%f = %f\n",lat0,lat1,lon0,lon1,((lat1-lat0)*(lat1-lat0) + (lon1-lon0)*(lon1-lon0)));
return ((lat1-lat0)*(lat1-lat0) + (lon1-lon0)*(lon1-lon0));
}
static bool
closeEnough(double distSquared)
{
// If absolute difference is less than 0.00045 then close (~50 meters of latitude)
// return (distSquared < (0.00045 * 0.00045));
// If absolute difference is less than 0.0009 then close (~100 meters of latitude)
// return (distSquared < (0.0009 * 0.0009));
// Some of the tidal stream locations are so far off that we need to return this to filter them:
return (distSquared < (0.00155 * 0.00155));
}
static bool
closeEnough(double distSquared, double degreesPerPixel)
{
double dist = sqrt(distSquared);
double pixels = dist / degreesPerPixel;
//debugf(1,"dist %f deg at %f deg per pix (%f pix per deg) is %f pixels\n",dist,degreesPerPixel,1.0/degreesPerPixel,pixels);
return (pixels < 20.0);
}
/* ----------------------------------------------------------------------------
* Main window
*/
DisplayWindow::DisplayWindow()
: QMainWindow( 0, "xqct main window", WDestructiveClose)
{
int id;
//settings = new QSettings();
settings = new KConfig("xqct"); // replaces QSettings(); reads file $HSYSDIR/xqctrc
const char *hsysdir = getenv(SYSCONFIGENV);
if (hsysdir)
settings->insertSearchPath(QSettings::Unix, hsysdir);
settings->insertSearchPath(QSettings::Windows, "/DSS");
settings->beginGroup("xqct");
int wx = settings->readNumEntry("x", -1);
int wy = settings->readNumEntry("y", -1);
if (wx != -1 && wy != -1)
move(wx, wy);
wx = settings->readNumEntry("width", -1);
wy = settings->readNumEntry("height", -1);
if (wx != -1 && wy != -1)
resize(wx, wy);
loadSettings();
// Create "File" menu
QPopupMenu * fileMenu = new QPopupMenu( this );
menuBar()->insertItem( "&File", fileMenu );
mruMenu = new MRUMenu(this, settings, NULL);
// mruMenu->setShortcuts(true); // useless as only activated after menu shown!
connect(mruMenu, SIGNAL(activatedText(const QString &, const QString &)), this, SLOT(loadNewMapFile(const QString &, const QString &)));
fileMenu->insertItem( "&Open Map...", this, SLOT( loadMap() ), CTRL+Key_O );
fileMenu->insertItem( "&Open Recent Map", mruMenu );
fileMenu->insertItem( "&Save Map As...", this, SLOT( saveScreen() ), CTRL+Key_S );
//fileMenu->insertItem( "&Save Map As...", this, SLOT( saveMap() ), CTRL+Key_S );
//fileMenu->insertItem( "Print Whole Map...", this, SLOT( printMap() ) ); // usually too big to be useful
fileMenu->insertItem( "&Print Map...", this, SLOT( printScreen() ), CTRL+Key_P );
fileMenu->insertSeparator();
fileMenu->insertItem( "&Quit", this, SLOT( quit() ), CTRL+Key_Q );
// Create "Edit" menu
editMenu = new QPopupMenu( this );
menuBar()->insertItem( "&Edit", editMenu );
id = editMenu->insertItem( "&Date...", this, SLOT( editDate() ), CTRL+Key_D);
id = editMenu->insertItem( "&Preferences", this, SLOT( editPrefs() ));
// XXX whatsThis
// Create "View" menu
viewMenu = new QPopupMenu( this );
menuBar()->insertItem( "&View", viewMenu );
tidalLevelMenu = new QPopupMenu(viewMenu);
connect(tidalLevelMenu, SIGNAL(aboutToShow()), this, SLOT(showTidalLevelMenu()));
id = viewMenu->insertItem("Tidal &Level Locations", tidalLevelMenu);
tidalStreamMenu = new QPopupMenu(viewMenu);
connect(tidalStreamMenu, SIGNAL(aboutToShow()), this, SLOT(showTidalStreamMenu()));
id = viewMenu->insertItem("Tidal &Diamond", tidalStreamMenu);
// Create "Zoom" menu
QPopupMenu *zoomMenu = new QPopupMenu(viewMenu);
id = zoomMenu->insertItem("&Full size", this, SLOT(changeZoom(int)), 0, 1);
id = zoomMenu->insertItem("&Half size", this, SLOT(changeZoom(int)), 0, 2);
id = zoomMenu->insertItem("&Quarter size", this, SLOT(changeZoom(int)), 0, 4);
id = zoomMenu->insertItem("&Eighth size", this, SLOT(changeZoom(int)), 0, 8);
id = viewMenu->insertItem("&Zoom", zoomMenu);
// Create "Help" menu
QPopupMenu * helpMenu = new QPopupMenu( this );
menuBar()->insertSeparator();
menuBar()->insertItem( "&Help", helpMenu );
helpMenu->insertItem( "&About", this, SLOT(about()), Key_F1 );
helpMenu->insertItem( "&Manual", this, SLOT(helpmanual()) );
helpMenu->insertSeparator();
helpMenu->insertItem( "What's &This", this, SLOT(whatsThis()), SHIFT+Key_F1 );
QToolBar * fileTools = new QToolBar( this, "chart toolbar" );
fileTools->setLabel( "Chart Toolbar" );
new QToolButton( QPixmap(fileopen), "Open Map", QString::null,
this, SLOT(loadMap()), fileTools, "open map" );
new QToolButton( QPixmap(filesave), "Save Map", QString::null,
this, SLOT(saveScreen()), fileTools, "save map" );
new QToolButton( QPixmap(fileprint), "Print Map", QString::null,
this, SLOT(printScreen()), fileTools, "print map" );
new QToolButton( QPixmap(calendaricon3), "Change Date", QString::null,
this, SLOT(editDate()), fileTools, "change date" );
// Main window contains a vertical box which groups:
// the map image, a slider, a label (status bar is at bottom)
QVBox *main = new QVBox(this);
qctimage = new QCTImage(main);
// for information:
//qctimage->getQct()->printMetadata(stdout);
connect(qctimage, SIGNAL(location(double,double,long)), this, SLOT(mouse_moved(double,double)));
connect(qctimage, SIGNAL(mouseWheel(Qt::Orientation, int)), this, SLOT(mouse_wheel(Qt::Orientation, int)));
connect(qctimage, SIGNAL(contextMenu(double,double)), this, SLOT(context_menu(double,double)));
slider = new QSlider(0, TIDECALC_PERDAY*3-1, 60/TIDECALC_INTERVAL_MINS, 0, QSlider::Horizontal, main, "slider");
slider->setFocus();
slider->setTickInterval(6);
slider->setTickmarks(QSlider::Below);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(slider_moved(int)));
// Coordinate display
QHBox *infobox = new QHBox(main);
dateLabel = new QLabel(infobox, "dat"); dateLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
latlonDegLabel = new QLabel(infobox, "deg"); latlonDegLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
latlonDMSLabel = new QLabel(infobox, "dms"); latlonDMSLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
bngLabel = new QLabel(infobox, "bng"); bngLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
// Choose the coming Saturday
QDate today = QDate::currentDate();
if (today.dayOfWeek() < 6)
{
// Monday..Sunday is 1..7 so if before weekend then choose Saturday
today = today.addDays(6 - today.dayOfWeek());
}
// Can't call setDate(today.year(), today.month(), today.day(), 0, 0)
// because that moves the slider which does the tide calcs and we're not
// ready for that yet so just set the time manually
slider_jtime = date_to_jtime(today.year(), today.month(), today.day(), 0, 0);
slider_offset = 0.0;
// Start collecting maps (this will continue in a background thread)
// ie. in the constructor it calls mapCollectionPtr->collectMaps();
mapCollectionPtr = new QCTCollection(DEFAULT_CHART_DIR);
// Internal data
tidalStreamList.clear();
tidalStreamList.setAutoDelete(true);
tidalLevelList.clear();
tidalLevelList.setAutoDelete(true);
tideCalcPtr = new TideCalc();
moonCalcPtr = new MoonCalc();
// Load the TCD file tide station database
tideCalcPtr->loadTideDatabase(DEFAULT_HARMONICS_FILE);
// Load the first map (last one used if possible)
if (!mruMenu->mostRecentEntry().isEmpty())
{
loadNewMapFile(mruMenu->mostRecentEntry(), mruMenu->mostRecentMetadata());
}
else
loadNewMapFile(DEFAULT_CHART);
setCentralWidget( main );
statusBar()->message("Ready", 1000);
}
DisplayWindow::~DisplayWindow()
{
saveMapMetadata();
saveSettings();
delete settings;
delete tideCalcPtr;
delete moonCalcPtr;
delete mapCollectionPtr;
}
/* ----------------------------------------------------------------------------
*/
void DisplayWindow::loadSettings()
{
#if 0
QStringList list;
QStringList::Iterator iter;
settings->endGroup(); // grrr kautoconfig leaves it in a random group
list = settings->entryList("/xqct");
for (iter = list.begin(); iter != list.end(); ++iter )
{
QString val = settings->readEntry(QString("/xqct/") + *iter, "<null>");
printf("entryList: %s = %s\n", (const char*) *iter, (const char*)val);
}
list = settings->subkeyList("/xqct");
for (iter = list.begin(); iter != list.end(); ++iter )
{
printf("subkeyList: %s\n", (const char*) *iter);
QStringList list2;
QStringList::Iterator iter2;
QString subkey("/xqct/");
subkey += *iter;
printf(" looking at %s\n", (const char*) subkey);
list2 = settings->entryList(subkey);
for (iter2 = list2.begin(); iter2 != list2.end(); ++iter2 )
{
printf(" entryList: %s\n", (const char*) *iter2);
}
}
#endif
cfg_TLmustBeOnChart = settings->readBoolEntry("TLmustBeOnChart", DEFAULT_TL_MUST_BE_ON_CHART);
cfg_TLmustBeUnique = settings->readBoolEntry("TLmustBeUnique", DEFAULT_TL_MUST_BE_UNIQUE);
cfg_TSmustBeOnChart = settings->readBoolEntry("TSmustBeOnChart", DEFAULT_TS_MUST_BE_ON_CHART);
cfg_TSmustBeUnique = settings->readBoolEntry("TSmustBeUnique", DEFAULT_TS_MUST_BE_UNIQUE);
cfg_TSmustHaveKnownRef = settings->readBoolEntry("TSmustHaveKnownRef", DEFAULT_TS_MUST_HAVE_KNOWN_REF);
// map 0,1,2,3 to 1,2,4,8
cfg_zoomOutLevel = 1 << settings->readNumEntry("zoomOutLevel", DEFAULT_ZOOM_OUT_LEVEL);
}
/* ----------------------------------------------------------------------------
*/
void DisplayWindow::saveSettings()
{
// map 1,2,4,8 to 0,1,2,3
int level = cfg_zoomOutLevel==8?3:(cfg_zoomOutLevel==4?2:(cfg_zoomOutLevel==2?1:0));
//printf("saveSettings zoom %d save %d\n", cfg_zoomOutLevel, level);
//printf("saveSettings TSmustBeUnique=%s\n", cfg_TSmustBeUnique?"true":"false");
settings->writeEntry("x", x());
settings->writeEntry("y", y());
settings->writeEntry("width", width());
settings->writeEntry("height", height());
// XXX the bools get written as integers (0 or 1) for some reason
// maybe because we're using KConfig instead of QSettings?
// It still works though.
// KAutoConfig likes to actually delete entries if the value is
// the same as the default (which is kind of nice if you later
// change the default) but here we just write every setting.
settings->writeEntry("TLmustBeOnChart", cfg_TLmustBeOnChart);
settings->writeEntry("TLmustBeUnique", cfg_TLmustBeUnique);
settings->writeEntry("TSmustBeOnChart", cfg_TSmustBeOnChart);
settings->writeEntry("TSmustBeUnique", cfg_TSmustBeUnique);
settings->writeEntry("TSmustHaveKnownRef", cfg_TSmustHaveKnownRef);
settings->writeEntry("zoomOutLevel", level);
}
/* ----------------------------------------------------------------------------
*/
void DisplayWindow::slider_moved(int pos)
{
slider_offset = pos * TIDECALC_INTERVAL_MINS;
debugf(1, "slider_moved pos %d is mins %f\n", pos, slider_offset);
// Update the current time in the status bar
dateLabel->setText(QString(jctime(slider_jtime + slider_offset)));
QApplication::setOverrideCursor(waitCursor);
plotTidalStreams();
plotTidalLevels();
QApplication::restoreOverrideCursor();
}
/* ----------------------------------------------------------------------------
* Mouse has moved so update the position text
*/
QString
coord_str_deg(double lat, double lon)
{
QString msg;
msg.sprintf("%8.4f %c, %8.4f %c", fabs(lat), lat>0?'N':'S', fabs(lon), lon>0?'E':'W');
return msg;
}
QString
coord_str_dms(double lat, double lon)
{
QString msg;
int neglat = (lat < 0);
int neglon = (lon < 0);
lat = fabs(lat);
lon = fabs(lon);
double latmin = lat - (int)lat;
double lonmin = lon - (int)lon;
msg.sprintf("%d* %.2f' %c, %d* %.2f' %c", (int)lat, latmin*60.0, neglat?'S':'N', (int)lon, lonmin*60.0, neglon?'W':'E');
msg.replace("*", QString(QChar(UNICODE_DEGREE)));
return msg;
}
QString
coord_str_bng(double lat, double lon)
{
QString msg;
return msg;
}
void
DisplayWindow::mouse_moved(double lat, double lon)
{
latlonDegLabel->setText(coord_str_deg(lat, lon));
latlonDMSLabel->setText(coord_str_dms(lat, lon));
bngLabel->setText(coord_str_bng(lat, lon));
}
/* ----------------------------------------------------------------------------
* Mouse wheel has been scrolled, zoom in or out
*/
void
DisplayWindow::mouse_wheel(Qt::Orientation orient, int delta)
{
if (orient == Qt::Horizontal)
return;
bool positive = delta > 0;
int newzoom = cfg_zoomOutLevel;
if (!positive && newzoom < 8)
{
newzoom <<= 1;
}
else if (positive && newzoom > 1)
{
newzoom >>= 1;
}
debugf(1,"mouse_wheel delta %d new level %d\n",delta,newzoom);
if (newzoom != cfg_zoomOutLevel)
changeZoom(newzoom);
}
/* ----------------------------------------------------------------------------
* Context menu requested at given latitude/longitude.
* Show list of maps which cover that location
* and look through all tidal levels and tidal streams to see if any are close
* and add a menu entry to get more details.
*/
void
DisplayWindow::context_menu(double latN, double lonE)
{
int id, nn;
debugf(1,"ContextMenu %f %f\n", latN, lonE);
// Create a menu
QPopupMenu *contextMenu = new QPopupMenu(this);
// Add a submenu listing available maps
mapContextMenu = new QPopupMenu(contextMenu);
id = contextMenu->insertItem("Maps at cursor", mapContextMenu);
QStringList maps = mapCollectionPtr->mapsListAtLatLon(latN, lonE);
QStringList::Iterator mapiter;
nn = 0;
for (mapiter = maps.begin(); mapiter != maps.end(); ++mapiter)
{
debugf(1,"Map at cursor: %s\n", (const char *)*mapiter);
id = mapContextMenu->insertItem(*mapiter, this, SLOT(context_menu_map(int)), 0, nn++);
}
if (nn == 0)
{
id = mapContextMenu->insertItem("None");
mapContextMenu->setItemEnabled(id, false);
}
// Add an entry for tidal levels
nn = 0;
for ( TidalLevel *tlpiter = tidalLevelList.first(); tlpiter; tlpiter = tidalLevelList.next() )
{
if (closeEnough(distanceBetween(latN, lonE, tlpiter->getLat(), tlpiter->getLon()), qctimage->getQct()->getDegreesPerPixel()))
{
//debugf(2, " %s at %f %f\n", (const char*)tlpiter->getName(), tlpiter->getLat(), tlpiter->getLon());
id = contextMenu->insertItem(tlpiter->getName(), this, SLOT(context_menu_level(int)), 0, nn);
}
nn++;
}
// Add an entry for tidal streams
nn = 0;
for ( TidalStream *tspiter = tidalStreamList.first(); tspiter; tspiter = tidalStreamList.next() )
{
if (closeEnough(distanceBetween(latN, lonE, tspiter->getLat(), tspiter->getLon()), qctimage->getQct()->getDegreesPerPixel()))
{
//debugf(2, " %s at %f %f ref %s\n", (const char*)tspiter->getName(), tspiter->getLat(), tspiter->getLon(), (const char*)tspiter->getRef());
id = contextMenu->insertItem(tspiter->getName() + " via "+tspiter->getRef(), this, SLOT(context_menu_stream(int)), 0, nn);
}
nn++;
}
contextMenu->exec(QCursor::pos());
delete contextMenu;
}
void
DisplayWindow::context_menu_map(int id)
{
debugf(1, "Request map number %d = %s\n", id, (const char*) mapContextMenu->text(id));
// Lookup map name by extracting it from the menu item chosen
// then lookup the map filename by querying the map collection
// then load it (will save the current map position, load new one
// and jump to same position)
QString fn = mapCollectionPtr->getFilenameForMap(mapContextMenu->text(id));
debugf(1, " so load filename %s\n", (const char*)fn);
loadNewMapFile(mapCollectionPtr->getFilenameForMap(mapContextMenu->text(id)));
}
void
DisplayWindow::context_menu_level(int id)
{
debugf(1, "Context menu Level %d\n", id);
int nn = 0;
for ( TidalLevel *tlpiter = tidalLevelList.first(); tlpiter; tlpiter = tidalLevelList.next() )
{
if (nn == id)
{
debugf(1, " is %s\n", (const char*)tlpiter->getName());
QMessageBox::information(this, "Tidal Level",
QString("<p>The tidal level %1 has:<ul>"
"<li>Mean High Water (Spring) %2"
"<li>Mean Low Water (Spring) %3"
"<li>Mean High Water (Neap) %4"
"<li>Mean Low Water (Neap) %5"
"<li>Current Level %6</ul>")
.arg(tlpiter->getName())
.arg(tlpiter->getMHWS())
.arg(tlpiter->getMLWS())
.arg(tlpiter->getMHWN())
.arg(tlpiter->getMLWN())
.arg(tlpiter->getCurrentLevel()));
break;
}
nn++;
}
}
void
DisplayWindow::context_menu_stream(int id)
{
debugf(1, "Context menu Stream %d\n", id);
int nn = 0;
for ( TidalStream *tspiter = tidalStreamList.first(); tspiter; tspiter = tidalStreamList.next() )
{
if (nn == id)
{
debugf(1, " is %s\n", (const char*)tspiter->getName());
float bearing = tspiter->getCurrentBearing();
QMessageBox::information(this, "Tidal Stream",
QString("<p>The tidal stream %1 is:<ul>"
"<li>referenced to the tidal station %2"
"<li>current bearing %3 degrees clockwise from North"
"<li>current rate %4 knots</ul>")
.arg(tspiter->getName())
.arg(tspiter->getRef())
.arg(bearing < 0 ? bearing+360.0 : bearing)
.arg(tspiter->getCurrentRate()));
break;
}
nn++;
}
}
/* ----------------------------------------------------------------------------
* Slot called by View menu (menu id is actually the desired zoom level 1,2,4,8)
* or from mouse_wheel
*/
void
DisplayWindow::changeZoom(int id)
{
debugf(1,"Changezoom from %d to %d\n", cfg_zoomOutLevel, id);
cfg_zoomOutLevel = id;
// XXX check map already loaded first??
double lat, lon;
qctimage->latLonOfCenter(&lat, &lon);
debugf(1,"map center before %f %f\n",lat,lon);
loadNewMapFile(mapFilename);
qctimage->scrollToLatLon(lat, lon);
debugf(1,"map center after %f %f\n",lat,lon);
}
/* ----------------------------------------------------------------------------
* Menu listing all Tidal Levels (where tide heights can be calculated)
*/
void
DisplayWindow::showTidalLevelMenu()
{
int id, nn = 0;
tidalLevelMenu->clear();
for ( TidalLevel *tlpiter = tidalLevelList.first(); tlpiter; tlpiter = tidalLevelList.next() )
{
QString label(tlpiter->getName());
id = tidalLevelMenu->insertItem(label, this, SLOT(tidalLevelMenuSelected(int)), 0, nn++);
}
if (!nn)
tidalLevelMenu->setItemEnabled(tidalLevelMenu->insertItem("No Tidal Levels loaded or none on this chart"), false);
}
void
DisplayWindow::tidalLevelMenuSelected(int id)
{
if (id < 0 || id > tidalLevelList.count()-1)
return;
qctimage->scrollToLatLon(tidalLevelList.at(id)->getLat(),
tidalLevelList.at(id)->getLon());
}
/* ----------------------------------------------------------------------------
* Menu listing all Tidal Streams (Tidal Diamonds)
*/
void
DisplayWindow::showTidalStreamMenu()
{
int id, nn = 0;
tidalStreamMenu->clear();
for ( TidalStream *tspiter = tidalStreamList.first(); tspiter; tspiter = tidalStreamList.next() )
{
QString label(tspiter->getName());
label += " on " + tspiter->getChart();
label += " via " + tspiter->getRef();
id = tidalStreamMenu->insertItem(label, this, SLOT(tidalStreamMenuSelected(int)), 0, nn++);
}
if (!nn)
tidalStreamMenu->setItemEnabled(tidalStreamMenu->insertItem("No Tidal Streams loaded or none on this chart"), false);
}
void
DisplayWindow::tidalStreamMenuSelected(int id)
{
if (id < 0 || id > tidalStreamList.count()-1)
return;
qctimage->scrollToLatLon(tidalStreamList.at(id)->getLat(),
tidalStreamList.at(id)->getLon());
}
/* ----------------------------------------------------------------------------
* loadMap - called from menu, prompts for a *.qct file to load
* loadNewMapFile - called from loadMap and from mruMenu loads map then tide
* loadMapFile - called from load(New)Map or changeZoom does the loading
* the distinction is that changing the zoom doesn't require tide reload
* and doesn't want previous zoom/center to be saved in the MRU menu
*/
void
DisplayWindow::saveMapMetadata()
{
double lat, lon;
// Store the current map, position and zoom in the MRU metadata
// This assumes nothing has fiddled with the most recent entry
// since loadMapFile last used mruMenu->add.
if (qctimage->latLonOfCenter(&lat, &lon))
{
QString metadata;
metadata.sprintf("%d;%f;%f", cfg_zoomOutLevel, lat, lon);
mruMenu->setMostRecentMetadata(metadata);
}
}
void
DisplayWindow::loadMap()
{
QString qctdir(DEFAULT_CHART_DIR);
QString qctname( QFileDialog::getOpenFileName(qctdir, "Maps and charts (*.qct)", this) );
if (qctname.isEmpty())
return;
loadNewMapFile(qctname);
}
void
DisplayWindow::loadNewMapFile(const QString &qctname, const QString &qctMetadata)
{
// Store the current map, position and zoom in the MRU metadata
// This assumes nothing has fiddled with the most recent entry
// since loadMapFile last used mruMenu->add.
saveMapMetadata();
// Load the new map and reposition/zoom it
loadMapFile(qctname, qctMetadata);
// Reload the tide info to get new arrows
loadTidalData();
// Plot the overlays
QApplication::setOverrideCursor(waitCursor);
plotTidalStreams();
plotTidalLevels();
QApplication::restoreOverrideCursor();
// Update the current time in the status bar
dateLabel->setText(QString(jctime(slider_jtime + slider_offset)));
}
void
DisplayWindow::loadMapFile(const QString &qctname, const QString &qctMetadata)
{
debugf(1, "loadMapFile %s\n",(const char*)qctname);
// Decode the metadata (zoom and center)
double lat = 0, lon = 0;
if (!qctMetadata.isEmpty())
{
QStringList parts = QStringList::split(';', qctMetadata);
if (!parts[0].isEmpty())
cfg_zoomOutLevel = parts[0].toInt();
if (!parts[1].isEmpty())
lat = parts[1].toDouble();
if (!parts[1].isEmpty())
lon = parts[2].toDouble();
}
// Free the previous image
qctimage->unload();
// Load the new image at the configured zoom level
// Failure refreshes the screen to remove old one
if (!qctimage->load(qctname, cfg_zoomOutLevel))
{
qctimage->updateContents();
return;
}
// Save as most recent entry
// If metadata given then preserve it for this file too
mruMenu->add(qctname);
mruMenu->setMostRecentMetadata(qctMetadata);
mapFilename = qctname;
// Set the window title to include the map name
QString title("XQCT - ");
title += qctimage->getQct()->getName();
debugf(1, "setCaption '%s'\n", (const char*)title);
setCaption(title);
// Metadata contains location of center to view
if (lat && lon)
{
qctimage->scrollToLatLon(lat, lon);
}
// Refresh the whole window
qctimage->updateContents();
}
/* ----------------------------------------------------------------------------
* loadTidalData reads all *.C1 and *.T1 files containing tidal information,
* sifts out the duplicates according to the cfg_ preferences
* and appends to the lists tidalLevelList and tidalStreamList.
*/
bool
DisplayWindow::loadTidalLevelFile(const QString &zipname, const QString &zippassword, const QString &filename)
{
#ifdef USE_UNZIP
ZipFile file(zipname);
file.setName(filename, zippassword);
if (!file.open(IO_ReadOnly))
return false;
#else
QFile file(filename);
debugf(1, "load %s\n", (const char*)file.name());
if (!file.open(IO_ReadOnly))
return false;
#endif
QString line;
while (file.readLine(line, TIDE_MAX_LINE_LEN) > 0)
{
//debugf(1, "TL::%s", (const char*)line);
TidalLevel *tlp, *tlpiter;
tlp = new TidalLevel(line);
if (!tlp->isOk())
{
delete tlp;
continue;
}
// Ignore tide levels not on this chart
if (cfg_TLmustBeOnChart && (qctimage->getQct()->getIdentifier() != tlp->getChart()))
{
//debugf(1, "TL: %s != %s\n", (const char*)qctimage->getQct()->getIdentifier(), (const char*)tlp->getChart());
delete tlp;
continue;
}
// Ignore tide levels with coords outside boundary of this chart
if (!qctimage->getQct()->coordInsideMap(tlp->getLat(), tlp->getLon()))
{
//debugf(1, "TL: %s at %f,%f is not on this chart\n", (const char*)tlp->getName(), tlp->getLat(), tlp->getLon());
delete tlp;
continue;
}
// Check for duplicates, just ignore the new one
// assuming they actually are identical and first is ok
if (cfg_TLmustBeUnique)
for ( tlpiter = tidalLevelList.first(); tlpiter; tlpiter = tidalLevelList.next() )
{
if (closeEnough(distanceBetween(tlpiter->getLat(), tlpiter->getLon(), tlp->getLat(), tlp->getLon()), qctimage->getQct()->getDegreesPerPixel()))
{
debugf(2," IGNORE %s - same location as %s\n", (const char*)tlp->getName(), (const char*)tlpiter->getName());
delete tlp;
tlp = 0;
break;
}
}
if (tlp == 0)
continue;
debugf(1, "Tide Level: Spring Tide range %.1f to %.1f at %s\n", tlp->getMLWS(), tlp->getMHWS(), (const char*)tlp->getName());
debugf(1, "Tide Level: Neap Tide range %.1f to %.1f at %s\n", tlp->getMLWN(), tlp->getMHWN(), (const char*)tlp->getName());
tidalLevelList.append(tlp);
}
file.close();
return true;
}
bool
DisplayWindow::loadTidalStreamFile(const QString &zipname, const QString &zippassword, const QString &filename)
{
#ifdef USE_UNZIP
ZipFile file(zipname);
file.setName(filename, zippassword);
if (!file.open(IO_ReadOnly))
return false;
#else
QFile file(filename);
debugf(1, "load %s\n", (const char*)file.name());
if (!file.open(IO_ReadOnly))
return false;
#endif
QString line;
while (file.readLine(line, TIDE_MAX_LINE_LEN) > 0)
{
TidalStream *tsp, *tspiter;
tsp = new TidalStream(line);
if (!tsp->isOk())
{
delete tsp;
continue;
}
// Ignore tide streams not on this chart
// Can be useful to see all diamonds from other charts though
// (assuming they are within the chart boundary checked below)
if (cfg_TSmustBeOnChart && (qctimage->getQct()->getIdentifier() != tsp->getChart()))
{
//printf("TS: %s != %s\n", (const char*)qctimage->getQct()->getIdentifier(), (const char*)ts.getChart());
delete tsp;
continue;
}
// Ignore tide levels off this chart
if (!qctimage->getQct()->coordInsideMap(tsp->getLat(), tsp->getLon()))
{
//printf("TS: %s at %f,%f is not on this chart\n", (const char*)ts.getName(), ts.getLat(), ts.getLon());
delete tsp;
continue;
}
//debugf(1, "READ %s\n", (const char*)line);
debugf(1, "Tidal Stream %s referenced to %s at %sW (%f, %f)\n", (const char*)tsp->getName(), (const char*)tsp->getRef(), tsp->refAtHW()? "H":"L", tsp->getLat(), tsp->getLon());
// Ignore tide streams if ref station is not in tide database
if (cfg_TSmustHaveKnownRef)
{
double lat, lon;
if (!tideCalcPtr->getStationLocation(tsp->getRef(), &lat, &lon))
{
log_message(LOG_I, stderr, "TidalStream ignored because %s is not a recognised location", (const char*)tsp->getRef());
debugf(2, " IGNORED %s - ref not in tide db %s\n", (const char*)tsp->getName(), (const char*)tsp->getRef());
delete tsp;
continue;
}
}
// See if there's already one at the location
if (cfg_TSmustBeUnique)
for ( tspiter = tidalStreamList.first(); tspiter; tspiter = tidalStreamList.next() )
{
if (closeEnough(distanceBetween(tspiter->getLat(), tspiter->getLon(), tsp->getLat(), tsp->getLon()), qctimage->getQct()->getDegreesPerPixel()))
{
// If both have the same reference station and location then they are
// hopefully identical (or we can't tell which is best) so ignore new one
if (tspiter->getRef() == tsp->getRef())
{
debugf(2, " IGNORED - same location AND ref station, so identical\n");
delete tsp;
tsp = 0;
break;
}
// Find out which one has the closest reference station
double reflat0, reflon0, reflat1, reflon1;
double refdist1, refdist2;
debugf(2, " compare %s\n", (const char*)tsp->getRef());
tideCalcPtr->getStationLocation(tsp->getRef(), &reflat0, &reflon0);
debugf(2, " to %s\n", (const char*)tspiter->getRef());
tideCalcPtr->getStationLocation(tspiter->getRef(), &reflat1, &reflon1);
refdist1 = distanceBetween(reflat0, reflon0, tsp->getLat(), tsp->getLon()); // new
refdist2 = distanceBetween(reflat1, reflon1, tsp->getLat(), tsp->getLon()); // old
debugf(2, " REJECT - already in the list %f vs %f\n", refdist1, refdist2);
// XXX choose whether to reject the new one or delete the existing one
// Remove the one which is furthest away
// Could also deliberately choose the one with the same reference station
// ref station used by the nearest suborbinate station
// (eg. if this tidal diamond is close to Dundee and Dundee is referenced to Aberdeen)
if (refdist2 > refdist1)
{
// Remove the old one it is further away
// Could use replace but then the append below would have to be skipped
// so easiest to just remove the old one here and fall through to append
debugf(2, " remove old one\n");
tidalStreamList.removeRef(tspiter);
}
else
{
// Ignore the new one it is further away
debugf(2, " ignore new one\n");
delete tsp;
tsp = 0;
}
break;
}
}
if (tsp == 0)
continue;
tidalStreamList.append(tsp);
}
file.close();
return true;