OpenCPN Partial API docs
Loading...
Searching...
No Matches
chartdb.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 "chartdb.h"
25
26#include <stdio.h>
27#include <math.h>
28
29// For compilers that support precompilation, includes "wx.h".
30#include <wx/wxprec.h>
31
32#ifndef WX_PRECOMP
33#include <wx/wx.h>
34#endif
35
36#include <wx/dir.h>
37#include <wx/progdlg.h>
38#include <wx/regex.h>
39#include <wx/stopwatch.h>
40#include <wx/tokenzr.h>
41
42#include <model/base_platform.h>
43#include <model/ocpn_utils.h>
44
45#include "canvas_config.h"
46#include "chartimg.h"
47#include "chcanv.h"
48#include "cm93.h"
49#include "config.h"
50#include "config_mgr.h"
51#include "dychart.h"
52#include "mbtiles.h"
53#include "s57chart.h"
54#include "s57_load.h"
55#include "thumbwin.h"
56#include "user_colors.h"
57
58#ifdef __ANDROID__
59#include "androidUTIL.h"
60#endif
61
62#ifdef ocpnUSE_GL
63#include "gl_chart_canvas.h"
64#endif
65
66extern ColorScheme GetColorScheme(); // library dependency
67
68class s52plib;
69extern s52plib *ps52plib; // library dependency
70
72
73std::vector<std::string> ChartDirectoryExcludedVector;
74
75bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
76
77static bool IsSingleChart(ChartBase *chart) {
78 if (chart == nullptr) return false;
79
80 // ..For each canvas...
81 for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
82 ChartCanvas *cc = g_canvasArray.Item(i);
83 if (cc && cc->m_singleChart == chart) {
84 return true;
85 }
86 }
87 return false;
88}
89// ============================================================================
90// ChartStack implementation
91// ============================================================================
92
93int ChartStack::GetCurrentEntrydbIndex() {
94 if (nEntry && (CurrentStackEntry >= 0) /*&& b_valid*/)
95 return DBIndex[CurrentStackEntry];
96 else
97 return -1;
98}
99
100void ChartStack::SetCurrentEntryFromdbIndex(int current_db_index) {
101 for (int i = 0; i < nEntry; i++) {
102 if (current_db_index == DBIndex[i]) CurrentStackEntry = i;
103 }
104}
105
106int ChartStack::GetDBIndex(int stack_index) {
107 if ((stack_index >= 0) && (stack_index < nEntry) && (stack_index < MAXSTACK))
108 return DBIndex[stack_index];
109 else
110 return -1;
111}
112
113void ChartStack::SetDBIndex(int stack_index, int db_index) {
114 if ((stack_index >= 0) && (stack_index < nEntry) && (stack_index < MAXSTACK))
115 DBIndex[stack_index] = db_index;
116}
117
118bool ChartStack::DoesStackContaindbIndex(int db_index) {
119 for (int i = 0; i < nEntry; i++) {
120 if (db_index == DBIndex[i]) return true;
121 }
122
123 return false;
124}
125
126void ChartStack::AddChart(int db_add) {
127 if (!ChartData) return;
128
129 if (!ChartData->IsValid()) return;
130
131 int db_index = db_add;
132
133 int j = nEntry;
134
135 if (db_index >= 0) {
136 j++;
137 nEntry = j;
138 SetDBIndex(j - 1, db_index);
139 }
140 // Remove exact duplicates, i.e. charts that have exactly the same file
141 // name and
142 // nearly the same mod time.
143 // These charts can be in the database due to having the exact same chart
144 // in different directories, as may be desired for some grouping schemes
145 // Note that if the target name is actually a directory, then windows fails
146 // to produce a valid file modification time. Detect GetFileTime() == 0,
147 // and skip the test in this case
148 for (int id = 0; id < j - 1; id++) {
149 if (GetDBIndex(id) != -1) {
150 auto &pm = ChartData->GetChartTableEntry(GetDBIndex(id));
151
152 for (int jd = id + 1; jd < j; jd++) {
153 if (GetDBIndex(jd) != -1) {
154 auto &pn = ChartData->GetChartTableEntry(GetDBIndex(jd));
155 if (pm.GetFileTime() && pn.GetFileTime()) {
156 if (labs(pm.GetFileTime() - pn.GetFileTime()) <
157 60) { // simple test
158 if (pn.GetpFileName()->IsSameAs(*(pm.GetpFileName())))
159 SetDBIndex(jd, -1); // mark to remove
160 }
161 }
162 }
163 }
164 }
165 }
166
167 int id = 0;
168 while ((id < j)) {
169 if (GetDBIndex(id) == -1) {
170 int jd = id + 1;
171 while (jd < j) {
172 int db_index = GetDBIndex(jd);
173 SetDBIndex(jd - 1, db_index);
174 jd++;
175 }
176
177 j--;
178 nEntry = j;
179
180 id = 0;
181 } else
182 id++;
183 }
184
185 // Sort the stack on scale
186 int swap = 1;
187 int ti;
188 while (swap == 1) {
189 swap = 0;
190 for (int i = 0; i < j - 1; i++) {
191 const ChartTableEntry &m = ChartData->GetChartTableEntry(GetDBIndex(i));
192 const ChartTableEntry &n =
193 ChartData->GetChartTableEntry(GetDBIndex(i + 1));
194
195 if (n.GetScale() < m.GetScale()) {
196 ti = GetDBIndex(i);
197 SetDBIndex(i, GetDBIndex(i + 1));
198 SetDBIndex(i + 1, ti);
199 swap = 1;
200 }
201 }
202 }
203}
204
205// ============================================================================
206// ChartDB implementation
207// ============================================================================
208
209ChartDB::ChartDB() {
210 pChartCache = new wxArrayPtrVoid;
211
212 SetValid(false); // until loaded or created
213 UnLockCache();
214
215 SetBusy(false);
216 m_ticks = 0;
217
218 // Report cache policy
219 if (g_memCacheLimit) {
220 wxString msg;
221 msg.Printf("ChartDB Cache policy: Application target is %d MBytes",
222 g_memCacheLimit / 1024);
223 wxLogMessage(msg);
224 } else {
225 wxString msg;
226 msg.Printf("ChartDB Cache policy: Max open chart limit is %d.",
227 g_nCacheLimit);
228 wxLogMessage(msg);
229 }
230
231 m_checkGroupIndex[0] = m_checkGroupIndex[1] = -1;
232 m_checkedTileOnly[0] = m_checkedTileOnly[1] = false;
233}
234
235ChartDB::~ChartDB() {
236 // Empty the cache
237 PurgeCache();
238
239 delete pChartCache;
240}
241
242bool ChartDB::LoadBinary(const wxString &filename,
243 ArrayOfCDI &dir_array_check) {
244 m_dir_array = dir_array_check;
245 return ChartDatabase::Read(filename);
246
247 // Check chartDirs against dir_array_check
248}
249
250void ChartDB::DeleteCacheEntry(CacheEntry *pce, bool bDelTexture,
251 const wxString &msg) {
252 ChartBase *ch = (ChartBase *)pce->pChart;
253
254 if (msg != wxEmptyString) {
255 wxLogMessage("%s%s", msg.c_str(), ch->GetFullPath().c_str());
256 }
257
258 // If this chart should happen to be in the thumbnail window....
259 if (pthumbwin) {
260 if (pthumbwin->pThumbChart == ch) pthumbwin->pThumbChart = NULL;
261 }
262
263#ifdef ocpnUSE_GL
264 // The glCanvas may be cacheing some information for this chart
266 g_glTextureManager->PurgeChartTextures(ch, bDelTexture);
267#endif
268
269 pChartCache->Remove(pce);
270 delete ch;
271 delete pce;
272}
273
274void ChartDB::DeleteCacheEntry(int i, bool bDelTexture, const wxString &msg) {
275 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
276 if (pce) DeleteCacheEntry(pce, bDelTexture, msg);
277}
278
279void ChartDB::PurgeCache() {
280 // Empty the cache
281 // wxLogMessage("Chart cache purge");
282
283 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
284 unsigned int nCache = pChartCache->GetCount();
285 for (unsigned int i = 0; i < nCache; i++) {
286 DeleteCacheEntry(0, true);
287 }
288 pChartCache->Clear();
289
290 m_cache_mutex.Unlock();
291 }
292}
293
294void ChartDB::PurgeCachePlugins() {
295 // Empty the cache
296 wxLogMessage("Chart cache PlugIn purge");
297
298 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
299 unsigned int nCache = pChartCache->GetCount();
300 unsigned int i = 0;
301 while (i < nCache) {
302 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
303 ChartBase *Ch = (ChartBase *)pce->pChart;
304
305 if (CHART_TYPE_PLUGIN == Ch->GetChartType()) {
306 DeleteCacheEntry(pce, true);
307
308 nCache = pChartCache->GetCount(); // restart the while loop
309 i = 0;
310
311 } else
312 i++;
313 }
314
315 m_cache_mutex.Unlock();
316 }
317}
318
319void ChartDB::ClearCacheInUseFlags() {
320 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
321 unsigned int nCache = pChartCache->GetCount();
322 for (unsigned int i = 0; i < nCache; i++) {
323 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
324 pce->b_in_use = false;
325 }
326 m_cache_mutex.Unlock();
327 }
328}
329
330// Try to purge and delete charts from the cache until the application
331// memory used is until the application memory used is less than {factor *
332// Limit} Purge charts on LRU policy
333void ChartDB::PurgeCacheUnusedCharts(double factor) {
334 // Use memory limited cache policy, if defined....
335 if (g_memCacheLimit) {
336 if (wxMUTEX_NO_ERROR == m_cache_mutex.TryLock()) {
337 // Check memory status to see if above limit
338 int mem_used;
339 platform::GetMemoryStatus(0, &mem_used);
340 int mem_limit = g_memCacheLimit * factor;
341
342 int nl = pChartCache->GetCount(); // max loop count, by definition
343
344 wxString msg("Purging unused chart from cache: ");
345 // printf("Try Purge count: %d\n", nl);
346 while ((mem_used > mem_limit) && (nl > 0)) {
347 if (pChartCache->GetCount() < 2) {
348 nl = 0;
349 break;
350 }
351
352 CacheEntry *pce = FindOldestDeleteCandidate(false);
353 if (pce) {
354 // don't purge background spooler
355 DeleteCacheEntry(pce, false /*true*/, msg);
356 // printf("DCE, new count is: %d\n", pChartCache->GetCount());
357 } else {
358 break;
359 }
360
361 platform::GetMemoryStatus(0, &mem_used);
362
363 nl--;
364 }
365 }
366 m_cache_mutex.Unlock();
367 }
368
369 // Else use chart count cache policy, if defined....
370 else if (g_nCacheLimit) {
371 if (wxMUTEX_NO_ERROR == m_cache_mutex.TryLock()) {
372 // Check chart count to see if above limit
373 double fac10 = factor * 10;
374 int chart_limit = g_nCacheLimit * fac10 / 10;
375
376 int nl = pChartCache->GetCount(); // max loop count, by definition
377
378 wxString msg("Purging unused chart from cache: ");
379 while ((nl > chart_limit) && (nl > 0)) {
380 if (pChartCache->GetCount() < 2) {
381 nl = 0;
382 break;
383 }
384
385 CacheEntry *pce = FindOldestDeleteCandidate(false);
386 if (pce) {
387 // don't purge background spooler
388 DeleteCacheEntry(pce, false /*true*/, msg);
389 } else {
390 break;
391 }
392
393 nl = pChartCache->GetCount();
394 }
395 }
396 m_cache_mutex.Unlock();
397 }
398}
399
400//-------------------------------------------------------------------------------------------------------
401// Create a Chart
402// This version creates a fully functional UI-capable chart.
403//-------------------------------------------------------------------------------------------------------
404
405ChartBase *ChartDB::GetChart(const wxChar *theFilePath,
406 ChartClassDescriptor &chart_desc) const {
407 wxFileName fn(theFilePath);
408
409 if (!fn.FileExists()) {
410 // Might be a directory
411 if (!wxDir::Exists(theFilePath)) {
412 wxLogMessage(" ...file does not exist: %s", theFilePath);
413 return NULL;
414 }
415 }
416 ChartBase *pch = NULL;
417
418 wxString chartExt = fn.GetExt().Upper();
419
420 if (chartExt == "XZ") {
421 wxString npath = theFilePath;
422 npath = npath.Left(npath.length() - 3);
423 wxFileName fn(npath);
424 chartExt = fn.GetExt().Upper();
425 }
426
427 if (chartExt == "KAP") {
428 pch = new ChartKAP;
429 } else if (chartExt == "GEO") {
430 pch = new ChartGEO;
431 } else if (chartExt == "MBTILES") {
432 pch = new ChartMbTiles;
433 } else if (chartExt == "000" || chartExt == "S57") {
434 LoadS57();
435 pch = new s57chart;
436 } else if (chart_desc.m_descriptor_type == PLUGIN_DESCRIPTOR) {
437 LoadS57();
438 ChartPlugInWrapper *cpiw = new ChartPlugInWrapper(chart_desc.m_class_name);
439 pch = (ChartBase *)cpiw;
440 }
441
442 else {
443 wxRegEx rxName("[0-9]+");
444 wxRegEx rxExt("[A-G]");
445 if (rxName.Matches(fn.GetName()) && rxExt.Matches(chartExt))
446 pch = new cm93compchart;
447 else {
448 // Might be a directory
449 if (wxDir::Exists(theFilePath)) pch = new cm93compchart;
450 }
451 }
452
453 return pch;
454}
455
456bool ChartDB::IsChartDirectoryExcluded(const std::string &chart_file) {
457 for (auto excluded_dir : ChartDirectoryExcludedVector) {
458 if (ocpn::startswith(chart_file, excluded_dir)) return true;
459 }
460 return false;
461}
462
463// Build a Chart Stack, and add the indicated chart to the stack, even if
464// the chart does not cover the lat/lon specification
465
466int ChartDB::BuildChartStack(ChartStack *cstk, float lat, float lon, int db_add,
467 int groupIndex) {
468 BuildChartStack(cstk, lat, lon, groupIndex);
469
470 if (db_add >= 0) cstk->AddChart(db_add);
471
472 return cstk->nEntry;
473}
474
475int ChartDB::BuildChartStack(ChartStack *cstk, float lat, float lon,
476 int groupIndex) {
477 int i = 0;
478 int j = 0;
479
480 if (!IsValid()) return 0; // Database is not properly initialized
481
482 if (!cstk) return 0; // Chartstack not ready yet
483
484 int nEntry = GetChartTableEntries();
485
486 for (int db_index = 0; db_index < nEntry; db_index++) {
487 const ChartTableEntry &cte = GetChartTableEntry(db_index);
488
489 // Skip any charts in Exclude array
490 if (IsChartDirectoryExcluded(cte.GetFullPath())) continue;
491
492 // Check to see if the candidate chart is in the currently active group
493 bool b_group_add = false;
494 if (groupIndex > 0) {
495 const int ng = cte.GetGroupArray().size();
496 for (int ig = 0; ig < ng; ig++) {
497 if (groupIndex == cte.GetGroupArray()[ig]) {
498 b_group_add = true;
499 break;
500 }
501 }
502 } else
503 b_group_add = true;
504
505 bool b_writable_add = true;
506 // On android, SDK > 29, we require that the directory of charts be
507 // "writable" as determined by Android Java file system
508#ifdef __ANDROID__
509 wxFileName fn(cte.GetFullSystemPath());
510 if (!androidIsDirWritable(fn.GetPath())) b_writable_add = false;
511#endif
512
513 bool b_pos_add = false;
514 if (b_group_add && b_writable_add) {
515 // Plugin loading is deferred, so the chart may have been disabled
516 // elsewhere. Tentatively reenable the chart so that it appears in the
517 // piano. It will get disabled later if really not useable
518 if (cte.GetChartType() == CHART_TYPE_PLUGIN) {
519 ChartTableEntry *pcte = (ChartTableEntry *)&cte;
520 pcte->ReEnable();
521 }
522
523 if (CheckPositionWithinChart(db_index, lat, lon) && (j < MAXSTACK))
524 b_pos_add = true;
525
526 // Check the special case where chart spans the international dateline
527 else if ((cte.GetLonMax() > 180.) && (cte.GetLonMin() < 180.)) {
528 if (CheckPositionWithinChart(db_index, lat, lon + 360.) &&
529 (j < MAXSTACK))
530 b_pos_add = true;
531 }
532 // Western hemisphere, some type of charts
533 else if ((cte.GetLonMax() > 180.) && (cte.GetLonMin() > 180.)) {
534 if (CheckPositionWithinChart(db_index, lat, lon + 360.) &&
535 (j < MAXSTACK))
536 b_pos_add = true;
537 }
538 }
539
540 bool b_available = true;
541 // Verify PlugIn charts are actually available
542 if (b_group_add && b_pos_add && (cte.GetChartType() == CHART_TYPE_PLUGIN)) {
543 ChartTableEntry *pcte = (ChartTableEntry *)&cte;
544 if (!IsChartAvailable(db_index)) {
545 pcte->SetAvailable(false);
546 b_available = false;
547 } else {
548 pcte->SetAvailable(true);
549 pcte->ReEnable();
550 }
551 }
552
553 if (b_group_add && b_pos_add && b_available) { // add it
554 j++;
555 cstk->nEntry = j;
556 cstk->SetDBIndex(j - 1, db_index);
557 }
558 }
559
560 cstk->nEntry = j;
561
562 // Remove exact duplicates, i.e. charts that have exactly the same file
563 // name and nearly the same mod time These charts can be in the database
564 // due to having the exact same chart in different directories, as may be
565 // desired for some grouping schemes Note that if the target name is
566 // actually a directory, then windows fails to produce a valid file
567 // modification time. Detect GetFileTime() == 0, and skip the test in this
568 // case
569 // Extended to also check for "identical" charts, having exact same
570 // EditionDate
571
572 for (int id = 0; id < j - 1; id++) {
573 if (cstk->GetDBIndex(id) != -1) {
574 const ChartTableEntry &ctem = GetChartTableEntry(cstk->GetDBIndex(id));
575
576 for (int jd = id + 1; jd < j; jd++) {
577 if (cstk->GetDBIndex(jd) != -1) {
578 const ChartTableEntry &cten =
579 GetChartTableEntry(cstk->GetDBIndex(jd));
580 bool bsameTime = false;
581 if (ctem.GetFileTime() && cten.GetFileTime()) {
582 if (labs(ctem.GetFileTime() - cten.GetFileTime()) < 60)
583 bsameTime = true;
584 }
585 if (ctem.GetChartEditionDate() == cten.GetChartEditionDate())
586 bsameTime = true;
587
588 if (bsameTime) {
589 if (cten.GetpFileName()->IsSameAs(*(ctem.GetpFileName())))
590 cstk->SetDBIndex(jd, -1); // mark to remove
591 }
592 }
593 }
594 }
595 }
596
597 int id = 0;
598 while ((id < j)) {
599 if (cstk->GetDBIndex(id) == -1) {
600 int jd = id + 1;
601 while (jd < j) {
602 int db_index = cstk->GetDBIndex(jd);
603 cstk->SetDBIndex(jd - 1, db_index);
604 jd++;
605 }
606
607 j--;
608 cstk->nEntry = j;
609
610 id = 0;
611 } else
612 id++;
613 }
614
615 // Sort the stack on scale
616 int swap = 1;
617 int ti;
618 while (swap == 1) {
619 swap = 0;
620 for (i = 0; i < j - 1; i++) {
621 const ChartTableEntry &m = GetChartTableEntry(cstk->GetDBIndex(i));
622 const ChartTableEntry &n = GetChartTableEntry(cstk->GetDBIndex(i + 1));
623
624 if (n.GetScale() < m.GetScale()) {
625 ti = cstk->GetDBIndex(i);
626 cstk->SetDBIndex(i, cstk->GetDBIndex(i + 1));
627 cstk->SetDBIndex(i + 1, ti);
628 swap = 1;
629 }
630 }
631 }
632
633 cstk->b_valid = true;
634
635 return j;
636}
637
638bool ChartDB::IsChartInGroup(const int db_index, const int group) {
639 ChartTableEntry *pt = (ChartTableEntry *)&GetChartTableEntry(db_index);
640
641 // Check to see if the candidate chart is in the designated group
642 bool b_in_group = false;
643 if (group > 0) {
644 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
645 if (group == pt->GetGroupArray()[ig]) {
646 b_in_group = true;
647 break;
648 }
649 }
650 } else
651 b_in_group = true;
652
653 return b_in_group;
654}
655
656bool ChartDB::IsENCInGroup(const int groupIndex) {
657 // Walk the database, looking in specified group for any vector chart
658 bool retVal = false;
659
660 for (int db_index = 0; db_index < GetChartTableEntries(); db_index++) {
661 const ChartTableEntry &cte = GetChartTableEntry(db_index);
662
663 // Check to see if the candidate chart is in the currently active group
664 bool b_group_add = false;
665 if (groupIndex > 0) {
666 const int ng = cte.GetGroupArray().size();
667 for (int ig = 0; ig < ng; ig++) {
668 if (groupIndex == cte.GetGroupArray()[ig]) {
669 b_group_add = true;
670 break;
671 }
672 }
673 } else
674 b_group_add = true;
675
676 if (b_group_add) {
677 if (cte.GetChartFamily() == CHART_FAMILY_VECTOR) {
678 retVal = true;
679 break; // the outer for loop
680 }
681 }
682 }
683
684 return retVal;
685}
686
687bool ChartDB::IsNonMBTileInGroup(const int groupIndex) {
688 // Walk the database, looking in specified group for anything other than
689 // MBTile Return true if so.
690 bool retVal = false;
691
692 for (int db_index = 0; db_index < GetChartTableEntries(); db_index++) {
693 const ChartTableEntry &cte = GetChartTableEntry(db_index);
694
695 // Check to see if the candidate chart is in the currently active group
696 bool b_group_add = false;
697 if (groupIndex > 0) {
698 const int ng = cte.GetGroupArray().size();
699 for (int ig = 0; ig < ng; ig++) {
700 if (groupIndex == cte.GetGroupArray()[ig]) {
701 b_group_add = true;
702 break;
703 }
704 }
705 } else
706 b_group_add = true;
707
708 if (b_group_add) {
709 if (cte.GetChartType() != CHART_TYPE_MBTILES) {
710 retVal = true;
711 break; // the outer for loop
712 }
713 }
714 }
715
716 return retVal;
717}
718
719//-------------------------------------------------------------------
720// Check to see it lat/lon is within a database chart at index
721//-------------------------------------------------------------------
722bool ChartDB::CheckPositionWithinChart(int index, float lat, float lon) {
723 const ChartTableEntry *pt = &GetChartTableEntry(index);
724
725 // First check on rough Bounding box
726
727 if ((lat <= pt->GetLatMax()) && (lat >= pt->GetLatMin()) &&
728 (lon >= pt->GetLonMin()) && (lon <= pt->GetLonMax())) {
729 // Double check on Primary Ply points polygon
730
731 bool bInside = G_FloatPtInPolygon((MyFlPoint *)pt->GetpPlyTable(),
732 pt->GetnPlyEntries(), lon, lat);
733
734 if (bInside) {
735 if (pt->GetnAuxPlyEntries()) {
736 for (int k = 0; k < pt->GetnAuxPlyEntries(); k++) {
737 bool bAuxInside =
738 G_FloatPtInPolygon((MyFlPoint *)pt->GetpAuxPlyTableEntry(k),
739 pt->GetAuxCntTableEntry(k), lon, lat);
740 if (bAuxInside) return true;
741 ;
742 }
743
744 } else
745 return true;
746 }
747 }
748
749 return false;
750}
751
752//-------------------------------------------------------------------
753// Compare Chart Stacks
754//-------------------------------------------------------------------
755bool ChartDB::EqualStacks(ChartStack *pa, ChartStack *pb) {
756 if ((pa == 0) || (pb == 0)) return false;
757 if ((!pa->b_valid) || (!pb->b_valid)) return false;
758 if (pa->nEntry != pb->nEntry) return false;
759
760 for (int i = 0; i < pa->nEntry; i++) {
761 if (pa->GetDBIndex(i) != pb->GetDBIndex(i)) return false;
762 }
763
764 return true;
765}
766
767//-------------------------------------------------------------------
768// Copy Chart Stacks
769//-------------------------------------------------------------------
770bool ChartDB::CopyStack(ChartStack *pa, ChartStack *pb) {
771 if ((pa == 0) || (pb == 0)) return false;
772 pa->nEntry = pb->nEntry;
773
774 for (int i = 0; i < pa->nEntry; i++) pa->SetDBIndex(i, pb->GetDBIndex(i));
775
776 pa->CurrentStackEntry = pb->CurrentStackEntry;
777
778 pa->b_valid = pb->b_valid;
779
780 return true;
781}
782
783wxString ChartDB::GetFullPath(ChartStack *ps, int stackindex) {
784 int dbIndex = ps->GetDBIndex(stackindex);
785 return GetChartTableEntry(dbIndex).GetFullSystemPath();
786}
787
788//-------------------------------------------------------------------
789// Get PlyPoint from stack
790//-------------------------------------------------------------------
791
792int ChartDB::GetCSPlyPoint(ChartStack *ps, int stackindex, int plyindex,
793 float *lat, float *lon) {
794 int dbIndex = ps->GetDBIndex(stackindex);
795 wxASSERT(dbIndex >= 0);
796
797 const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
798 if (entry.GetnPlyEntries()) {
799 float *fp = entry.GetpPlyTable();
800 fp += plyindex * 2;
801 *lat = *fp;
802 fp++;
803 *lon = *fp;
804 }
805
806 return entry.GetnPlyEntries();
807}
808
809//-------------------------------------------------------------------
810// Get Chart Scale
811//-------------------------------------------------------------------
812int ChartDB::GetStackChartScale(ChartStack *ps, int stackindex, char *buf,
813 int nbuf) {
814 int dbindex = ps->GetDBIndex(stackindex);
815 wxASSERT(dbindex >= 0);
816
817 const ChartTableEntry &entry = GetChartTableEntry(dbindex);
818 int sc = entry.GetScale();
819 if (buf) sprintf(buf, "%d", sc);
820
821 return sc;
822}
823
824//-------------------------------------------------------------------
825// Find ChartStack entry index corresponding to Full Path name, if present
826//-------------------------------------------------------------------
827int ChartDB::GetStackEntry(ChartStack *ps, wxString fp) {
828 for (int i = 0; i < ps->nEntry; i++) {
829 const ChartTableEntry &entry = GetChartTableEntry(ps->GetDBIndex(i));
830 if (fp.IsSameAs(entry.GetFullSystemPath())) return i;
831 }
832
833 return -1;
834}
835
836//-------------------------------------------------------------------
837// Get CSChart Type
838//-------------------------------------------------------------------
839ChartTypeEnum ChartDB::GetCSChartType(ChartStack *ps, int stackindex) {
840 if (IsValid()) {
841 int dbindex = ps->GetDBIndex(stackindex);
842 if (dbindex >= 0)
843 return (ChartTypeEnum)GetChartTableEntry(dbindex).GetChartType();
844 }
845 return CHART_TYPE_UNKNOWN;
846}
847
848ChartFamilyEnum ChartDB::GetCSChartFamily(ChartStack *ps, int stackindex) {
849 if (IsValid()) {
850 int dbindex = ps->GetDBIndex(stackindex);
851 if (dbindex >= 0) {
852 const ChartTableEntry &entry = GetChartTableEntry(dbindex);
853
854 ChartTypeEnum type = (ChartTypeEnum)entry.GetChartType();
855 switch (type) {
856 case CHART_TYPE_KAP:
857 return CHART_FAMILY_RASTER;
858 case CHART_TYPE_GEO:
859 return CHART_FAMILY_RASTER;
860 case CHART_TYPE_S57:
861 return CHART_FAMILY_VECTOR;
862 case CHART_TYPE_CM93:
863 return CHART_FAMILY_VECTOR;
864 case CHART_TYPE_CM93COMP:
865 return CHART_FAMILY_VECTOR;
866 case CHART_TYPE_DUMMY:
867 return CHART_FAMILY_RASTER;
868 default:
869 return CHART_FAMILY_UNKNOWN;
870 }
871 }
872 }
873 return CHART_FAMILY_UNKNOWN;
874}
875
876std::vector<int> ChartDB::GetCSArray(ChartStack *ps) {
877 std::vector<int> ret;
878
879 if (ps) {
880 ret.reserve(ps->nEntry);
881 for (int i = 0; i < ps->nEntry; i++) {
882 ret.push_back(ps->GetDBIndex(i));
883 }
884 }
885
886 return ret;
887}
888
889bool ChartDB::IsChartInCache(int dbindex) {
890 bool bInCache = false;
891
892 // Search the cache
893 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
894 unsigned int nCache = pChartCache->GetCount();
895 for (unsigned int i = 0; i < nCache; i++) {
896 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
897 if (pce->dbIndex == dbindex) {
898 if (pce->pChart != 0 && ((ChartBase *)pce->pChart)->IsReadyToRender())
899 bInCache = true;
900 break;
901 }
902 }
903 m_cache_mutex.Unlock();
904 }
905
906 return bInCache;
907}
908
909bool ChartDB::IsChartInCache(wxString path) {
910 bool bInCache = false;
911 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
912 // Search the cache
913 unsigned int nCache = pChartCache->GetCount();
914 for (unsigned int i = 0; i < nCache; i++) {
915 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
916 if (pce->FullPath == path) {
917 if (pce->pChart != 0 && ((ChartBase *)pce->pChart)->IsReadyToRender())
918 bInCache = true;
919 break;
920 }
921 }
922
923 m_cache_mutex.Unlock();
924 }
925 return bInCache;
926}
927
928bool ChartDB::IsChartLocked(int index) {
929 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
930 unsigned int nCache = pChartCache->GetCount();
931 for (unsigned int i = 0; i < nCache; i++) {
932 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
933 if (pce->dbIndex == index) {
934 bool ret = pce->n_lock > 0;
935 m_cache_mutex.Unlock();
936 return ret;
937 }
938 }
939 m_cache_mutex.Unlock();
940 }
941
942 return false;
943}
944
945bool ChartDB::LockCacheChart(int index) {
946 // Search the cache
947 bool ret = false;
948 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
949 unsigned int nCache = pChartCache->GetCount();
950 for (unsigned int i = 0; i < nCache; i++) {
951 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
952 if (pce->dbIndex == index) {
953 pce->n_lock++;
954 ret = true;
955 break;
956 }
957 }
958 m_cache_mutex.Unlock();
959 }
960 return ret;
961}
962
963void ChartDB::UnLockCacheChart(int index) {
964 // Search the cache
965 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
966 unsigned int nCache = pChartCache->GetCount();
967 for (unsigned int i = 0; i < nCache; i++) {
968 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
969 if (pce->dbIndex == index) {
970 if (pce->n_lock > 0) pce->n_lock--;
971 break;
972 }
973 }
974 m_cache_mutex.Unlock();
975 }
976}
977
978void ChartDB::UnLockAllCacheCharts() {
979 // Walk the cache
980 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
981 unsigned int nCache = pChartCache->GetCount();
982 for (unsigned int i = 0; i < nCache; i++) {
983 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
984 if (pce->n_lock > 0) pce->n_lock--;
985 }
986 m_cache_mutex.Unlock();
987 }
988}
989
990//-------------------------------------------------------------------
991// Open Chart
992//-------------------------------------------------------------------
993ChartBase *ChartDB::OpenChartFromDB(int index, ChartInitFlag init_flag) {
994 return OpenChartUsingCache(index, init_flag);
995}
996
997ChartBase *ChartDB::OpenChartFromDB(wxString chart_path,
998 ChartInitFlag init_flag) {
999 int dbii = FinddbIndex(chart_path);
1000 return OpenChartUsingCache(dbii, init_flag);
1001}
1002
1003ChartBase *ChartDB::OpenChartFromStack(ChartStack *pStack, int StackEntry,
1004 ChartInitFlag init_flag) {
1005 return OpenChartUsingCache(pStack->GetDBIndex(StackEntry), init_flag);
1006}
1007
1008ChartBase *ChartDB::OpenChartFromDBAndLock(int index, ChartInitFlag init_flag,
1009 bool lock) {
1010 wxCriticalSectionLocker locker(m_critSect);
1011 ChartBase *pret = OpenChartUsingCache(index, init_flag);
1012 if (lock && pret) LockCacheChart(index);
1013 return pret;
1014}
1015
1016ChartBase *ChartDB::OpenChartFromDBAndLock(wxString chart_path,
1017 ChartInitFlag init_flag) {
1018 int dbii = FinddbIndex(chart_path);
1019 return OpenChartFromDBAndLock(dbii, init_flag);
1020}
1021
1022CacheEntry *ChartDB::FindOldestDeleteCandidate(bool blog) {
1023 CacheEntry *pret = 0;
1024
1025 unsigned int nCache = pChartCache->GetCount();
1026 if (nCache > 1) {
1027 if (blog) wxLogMessage("Searching chart cache for oldest entry");
1028 int LRUTime = m_ticks;
1029 int iOldest = 0;
1030 for (unsigned int i = 0; i < nCache; i++) {
1031 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(i));
1032 if (pce->RecentTime < LRUTime && !pce->n_lock) {
1033 if (!IsSingleChart((ChartBase *)(pce->pChart))) {
1034 // Protect basemap MBTiles from cache eviction
1035 ChartBase *pChart = (ChartBase *)(pce->pChart);
1036 if (pChart && pChart->GetChartType() == CHART_TYPE_MBTILES) {
1037 wxFileName fn(pChart->GetFullPath());
1038 if (fn.GetPath().Lower().Contains("basemap")) continue;
1039 }
1040 LRUTime = pce->RecentTime;
1041 iOldest = i;
1042 }
1043 }
1044 }
1045 int dt = m_ticks - LRUTime;
1046
1047 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(iOldest));
1048 ChartBase *pDeleteCandidate = (ChartBase *)(pce->pChart);
1049
1050 if (!pce->n_lock && !IsSingleChart(pDeleteCandidate)) {
1051 if (blog)
1052 wxLogMessage("Oldest unlocked cache index is %d, delta t is %d",
1053 iOldest, dt);
1054
1055 pret = pce;
1056 } else
1057 wxLogMessage("All chart in cache locked, size: %d", nCache);
1058 }
1059
1060 return pret;
1061}
1062
1063ChartBase *ChartDB::OpenChartUsingCache(int dbindex, ChartInitFlag init_flag) {
1064 if ((dbindex < 0) || (dbindex > GetChartTableEntries() - 1)) return NULL;
1065
1066 // printf("Opening chart %d lock: %d\n", dbindex, m_b_locked);
1067
1068 const ChartTableEntry &cte = GetChartTableEntry(dbindex);
1069 wxString ChartFullPath = cte.GetFullSystemPath();
1070 ChartTypeEnum chart_type = (ChartTypeEnum)cte.GetChartType();
1071 ChartFamilyEnum chart_family = (ChartFamilyEnum)cte.GetChartFamily();
1072
1073 wxString msg1;
1074 msg1.Printf("OpenChartUsingCache: type %d ", chart_type);
1075 // wxLogMessage(msg1 + ChartFullPath);
1076
1077 if (cte.GetLatMax() > 90.0) // Chart has been disabled...
1078 return NULL;
1079
1080 ChartBase *Ch = NULL;
1081 CacheEntry *pce = NULL;
1082 int old_lock = 0;
1083
1084 bool bInCache = false;
1085
1086 // Search the cache
1087 {
1088 wxMutexLocker lock(m_cache_mutex);
1089
1090 unsigned int nCache = pChartCache->GetCount();
1091 m_ticks++;
1092 for (unsigned int i = 0; i < nCache; i++) {
1093 pce = (CacheEntry *)(pChartCache->Item(i));
1094 if (pce->FullPath == ChartFullPath) {
1095 Ch = (ChartBase *)pce->pChart;
1096 bInCache = true;
1097 break;
1098 }
1099 }
1100
1101 if (bInCache) {
1102 wxString msg;
1103 msg.Printf("OpenChartUsingCache, IN cache: cache size: %d\n",
1104 (int)pChartCache->GetCount());
1105 // wxLogMessage(msg);
1106 if (FULL_INIT == init_flag) // asking for full init?
1107 {
1108 if (Ch->IsReadyToRender()) {
1109 if (pce) {
1110 pce->RecentTime = m_ticks; // chart is OK
1111 pce->b_in_use = true;
1112 }
1113 return Ch;
1114 } else {
1115 if (pthumbwin && pthumbwin->pThumbChart == Ch)
1116 pthumbwin->pThumbChart = NULL;
1117 delete Ch; // chart is not useable
1118 old_lock = pce->n_lock;
1119 pChartCache->Remove(pce); // so remove it
1120 delete pce;
1121
1122 bInCache = false;
1123 }
1124 } else // assume if in cache, the chart can do thumbnails
1125 {
1126 if (pce) {
1127 pce->RecentTime = m_ticks;
1128 pce->b_in_use = true;
1129 }
1130 return Ch;
1131 }
1132 }
1133
1134 if (!bInCache) // not in cache
1135 {
1136 SetBusy(true);
1137 if (!m_b_locked) {
1138 // Use memory limited cache policy, if defined....
1139 if (g_memCacheLimit) {
1140 // Check memory status to see if enough room to open another chart
1141 int mem_used;
1142 platform::GetMemoryStatus(0, &mem_used);
1143
1144 wxString msg;
1145 msg.Printf("OpenChartUsingCache, NOT in cache: cache size: %d\n",
1146 (int)pChartCache->GetCount());
1147 wxLogMessage(msg);
1148 wxString msg1;
1149 msg1.Printf(" OpenChartUsingCache: type %d ", chart_type);
1150 wxLogMessage(msg1 + ChartFullPath);
1151
1152 if ((mem_used > g_memCacheLimit * 8 / 10) &&
1153 (pChartCache->GetCount() > 2)) {
1154 wxString msg("Removing oldest chart from cache: ");
1155 while (1) {
1156 CacheEntry *pce = FindOldestDeleteCandidate(true);
1157 if (pce == 0) break; // no possible delete candidate
1158
1159 // purge texture cache, really need memory here
1160 DeleteCacheEntry(pce, true, msg);
1161
1162 platform::GetMemoryStatus(0, &mem_used);
1163 if ((mem_used < g_memCacheLimit * 8 / 10) ||
1164 (pChartCache->GetCount() <= 2))
1165 break;
1166
1167 } // while
1168 }
1169 }
1170
1171 else // Use n chart cache policy, if memory-limit policy is not used
1172 {
1173 // Limit cache to n charts, tossing out the oldest when space is
1174 // needed
1175 unsigned int nCache = pChartCache->GetCount();
1176 if (nCache > (unsigned int)g_nCacheLimit && nCache > 2) {
1177 wxString msg("Removing oldest chart from cache: ");
1178 while (nCache > (unsigned int)g_nCacheLimit) {
1179 CacheEntry *pce = FindOldestDeleteCandidate(true);
1180 if (pce == 0) break;
1181
1182 DeleteCacheEntry(pce, true, msg);
1183 nCache--;
1184 }
1185 }
1186 }
1187 }
1188 }
1189 } // unlock
1190
1191 if (!bInCache) // not in cache
1192 {
1193 wxLogMessage("Creating new chart");
1194
1195 if (chart_type == CHART_TYPE_KAP)
1196 Ch = new ChartKAP();
1197
1198 else if (chart_type == CHART_TYPE_GEO)
1199 Ch = new ChartGEO();
1200
1201 else if (chart_type == CHART_TYPE_MBTILES)
1202 Ch = new ChartMbTiles();
1203
1204 else if (chart_type == CHART_TYPE_S57) {
1205 LoadS57();
1206 Ch = new s57chart();
1207 s57chart *Chs57 = static_cast<s57chart *>(Ch);
1208
1209 Chs57->SetNativeScale(cte.GetScale());
1210
1211 // Explicitely set the chart extents from the database to
1212 // support the case wherein the SENC file has not yet been built
1213 Extent ext;
1214 ext.NLAT = cte.GetLatMax();
1215 ext.SLAT = cte.GetLatMin();
1216 ext.WLON = cte.GetLonMin();
1217 ext.ELON = cte.GetLonMax();
1218 Chs57->SetFullExtent(ext);
1219 }
1220
1221 else if (chart_type == CHART_TYPE_CM93) {
1222 LoadS57();
1223 Ch = new cm93chart();
1224 cm93chart *Chcm93 = static_cast<cm93chart *>(Ch);
1225
1226 Chcm93->SetNativeScale(cte.GetScale());
1227
1228 // Explicitely set the chart extents from the database to
1229 // support the case wherein the SENC file has not yet been built
1230 Extent ext;
1231 ext.NLAT = cte.GetLatMax();
1232 ext.SLAT = cte.GetLatMin();
1233 ext.WLON = cte.GetLonMin();
1234 ext.ELON = cte.GetLonMax();
1235 Chcm93->SetFullExtent(ext);
1236 }
1237
1238 else if (chart_type == CHART_TYPE_CM93COMP) {
1239 LoadS57();
1240 Ch = new cm93compchart();
1241
1242 cm93compchart *Chcm93 = static_cast<cm93compchart *>(Ch);
1243
1244 Chcm93->SetNativeScale(cte.GetScale());
1245
1246 // Explicitely set the chart extents from the database to
1247 // support the case wherein the SENC file has not yet been built
1248 Extent ext;
1249 ext.NLAT = cte.GetLatMax();
1250 ext.SLAT = cte.GetLatMin();
1251 ext.WLON = cte.GetLonMin();
1252 ext.ELON = cte.GetLonMax();
1253 Chcm93->SetFullExtent(ext);
1254 }
1255
1256 else if (chart_type == CHART_TYPE_PLUGIN) {
1257 wxFileName fn(ChartFullPath);
1258 wxString ext = fn.GetExt();
1259 ext.Prepend("*.");
1260 wxString ext_upper = ext.MakeUpper();
1261 wxString ext_lower = ext.MakeLower();
1262 wxString chart_class_name;
1263
1264 // Search the array of chart class descriptors to find a match
1265 // bewteen the search mask and the the chart file extension
1266
1267 for (auto &cd : m_ChartClassDescriptorArray) {
1268 if (cd.m_descriptor_type == PLUGIN_DESCRIPTOR) {
1269 if (cd.m_search_mask == ext_upper) {
1270 chart_class_name = cd.m_class_name;
1271 break;
1272 }
1273 if (cd.m_search_mask == ext_lower) {
1274 chart_class_name = cd.m_class_name;
1275 break;
1276 }
1277 if (ChartFullPath.Matches(cd.m_search_mask)) {
1278 chart_class_name = cd.m_class_name;
1279 break;
1280 }
1281 }
1282 }
1283
1284 // chart_class_name = cte.GetChartClassName();
1285 if (chart_class_name.Len()) {
1286 ChartPlugInWrapper *cpiw = new ChartPlugInWrapper(chart_class_name);
1287 Ch = (ChartBase *)cpiw;
1288 if (chart_family == CHART_FAMILY_VECTOR) LoadS57();
1289 }
1290 }
1291
1292 else {
1293 Ch = NULL;
1294 wxLogMessage("Unknown chart type");
1295 }
1296
1297 if (Ch) {
1298 InitReturn ir;
1299
1300 s52plib *plib = ps52plib;
1301 wxString msg_fn(ChartFullPath);
1302 msg_fn.Replace("%", "%%");
1303
1304 // Vector charts need a PLIB for useful display....
1305 if ((chart_family != CHART_FAMILY_VECTOR) ||
1306 ((chart_family == CHART_FAMILY_VECTOR) && plib)) {
1307 wxLogMessage(wxString::Format("Initializing Chart %s", msg_fn.c_str()));
1308
1309 ir = Ch->Init(ChartFullPath, init_flag); // using the passed flag
1310 Ch->SetColorScheme(user_colors::GetColorScheme());
1311 } else {
1312 wxLogMessage(wxString::Format(" No PLIB, Skipping vector chart %s",
1313 msg_fn.c_str()));
1314
1315 ir = INIT_FAIL_REMOVE;
1316 }
1317
1318 if (INIT_OK == ir) {
1319 // always cache after a new chart has been created
1320 // or it may leak CacheEntry in createthumbnail
1321 // if(FULL_INIT == init_flag)
1322 {
1323 pce = new CacheEntry;
1324 pce->FullPath = ChartFullPath;
1325 pce->pChart = Ch;
1326 pce->dbIndex = dbindex;
1327 // printf(" Adding chart %d\n",
1328 // dbindex);
1329 pce->RecentTime = m_ticks;
1330 pce->n_lock = old_lock;
1331
1332 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
1333 pChartCache->Add((void *)pce);
1334 m_cache_mutex.Unlock();
1335 } else {
1336 delete pce;
1337 }
1338 }
1339
1340 // A performance optimization.
1341 // Hide this chart's MBTiles overlay on initial MBTile chart load, or
1342 // reload after cache purge. This can help avoid excessively long
1343 // startup and group switch time when large tilesets are in use. See
1344 // FS#2601 Further optimization: If any chart group being shown
1345 // contains only MBTiles, and the target file is less than 5 GB in
1346 // size,
1347 // then allow immediate opening. Otherwise, add this chart to the
1348 // "no-show" array for each chart.
1349 if (chart_type == CHART_TYPE_MBTILES) {
1350 wxFileName tileFile(ChartFullPath);
1351 // Size test for 5 GByte
1352 wxULongLong tileSizeMB = tileFile.GetSize() >> 20;
1353
1354 // Auto-show MBTiles in basemap directories
1355 bool isBasemap = tileFile.GetPath().Lower().Contains("basemap");
1356
1357 auto &config_array = ConfigMgr::Get().GetCanvasConfigArray();
1358
1359 if (!isBasemap && (!CheckAnyCanvasExclusiveTileGroup() ||
1360 (tileSizeMB.GetLo() > 5000))) {
1361 // Check to see if the tile has been "clicked" in either canvas.
1362 // If so, do not add to no-show array again.
1363 bool b_clicked = false;
1364 canvasConfig *cc;
1365 ChartCanvas *canvas = NULL;
1366
1367 switch (g_canvasConfig) {
1368 case 1:
1369 cc = config_array.Item(0);
1370 if (cc) {
1371 ChartCanvas *canvas = cc->canvas;
1372 if (canvas)
1373 b_clicked |= canvas->IsTileOverlayIndexInYesShow(dbindex);
1374 }
1375 cc = config_array.Item(1);
1376 if (cc) {
1377 ChartCanvas *canvas = cc->canvas;
1378 if (canvas)
1379 b_clicked |= canvas->IsTileOverlayIndexInYesShow(dbindex);
1380 }
1381 break;
1382 default:
1383 cc = config_array.Item(0);
1384 if (cc) {
1385 ChartCanvas *canvas = cc->canvas;
1386 if (canvas)
1387 b_clicked |= canvas->IsTileOverlayIndexInYesShow(dbindex);
1388 }
1389 break;
1390 }
1391
1392 // Add to all canvas noshow arrays
1393 if (!b_clicked) {
1394 switch (g_canvasConfig) {
1395 case 1:
1396 cc = config_array.Item(0);
1397 if (cc) {
1398 ChartCanvas *canvas = cc->canvas;
1399 if (canvas) canvas->AddTileOverlayIndexToNoShow(dbindex);
1400 }
1401 cc = config_array.Item(1);
1402 if (cc) {
1403 ChartCanvas *canvas = cc->canvas;
1404 if (canvas) canvas->AddTileOverlayIndexToNoShow(dbindex);
1405 }
1406 break;
1407 default:
1408 cc = config_array.Item(0);
1409 if (cc) {
1410 ChartCanvas *canvas = cc->canvas;
1411 if (canvas) canvas->AddTileOverlayIndexToNoShow(dbindex);
1412 }
1413 break;
1414 }
1415 }
1416 }
1417 }
1418 } else if (INIT_FAIL_REMOVE == ir) // some problem in chart Init()
1419 {
1420 wxLogMessage(
1421 wxString::Format("Problem initializing Chart %s", msg_fn.c_str()));
1422
1423 delete Ch;
1424 Ch = NULL;
1425
1426 // Mark this chart in the database, so that it will not be seen
1427 // during this run, but will stay in the database
1428 DisableChart(ChartFullPath);
1429 } else if ((INIT_FAIL_RETRY == ir) ||
1430 (INIT_FAIL_NOERROR ==
1431 ir)) // recoverable problem in chart Init()
1432 {
1433 wxLogMessage(wxString::Format(
1434 "Recoverable problem initializing Chart %s", msg_fn.c_str()));
1435 delete Ch;
1436 Ch = NULL;
1437 }
1438
1439 if (INIT_OK != ir) {
1440 if (1 /*INIT_FAIL_NOERROR != ir*/) {
1441 wxLogMessage(
1442 wxString::Format(" OpenChartFromStack... Error opening "
1443 "chart %s ... return code %d",
1444 msg_fn.c_str(), ir));
1445 }
1446 }
1447 }
1448
1449 SetBusy(false);
1450
1451 return Ch;
1452 }
1453
1454 return NULL;
1455}
1456
1457//
1458bool ChartDB::DeleteCacheChart(ChartBase *pDeleteCandidate) {
1459 bool retval = false;
1460
1461 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
1462 if (!IsSingleChart(pDeleteCandidate)) {
1463 // Find the chart in the cache
1464 CacheEntry *pce = NULL;
1465 for (unsigned int i = 0; i < pChartCache->GetCount(); i++) {
1466 pce = (CacheEntry *)(pChartCache->Item(i));
1467 if ((ChartBase *)(pce->pChart) == pDeleteCandidate) {
1468 break;
1469 }
1470 }
1471
1472 if (pce) {
1473 if (pce->n_lock > 0) pce->n_lock--;
1474
1475 if (pce->n_lock == 0) {
1476 DeleteCacheEntry(pce);
1477 retval = true;
1478 }
1479 }
1480 }
1481 m_cache_mutex.Unlock();
1482 }
1483
1484 return retval;
1485}
1486
1487/*
1488 */
1489void ChartDB::ApplyColorSchemeToCachedCharts(ColorScheme cs) {
1490 ChartBase *Ch;
1491 CacheEntry *pce;
1492#ifdef ocpnUSE_GL
1493 // The background texture cache threads might be running
1494 if (g_glTextureManager) g_glTextureManager->PurgeJobList();
1495#endif
1496 // Search the cache
1497
1498 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
1499 unsigned int nCache = pChartCache->GetCount();
1500 for (unsigned int i = 0; i < nCache; i++) {
1501 pce = (CacheEntry *)(pChartCache->Item(i));
1502 Ch = (ChartBase *)pce->pChart;
1503 if (Ch) Ch->SetColorScheme(cs, true);
1504 }
1505
1506 m_cache_mutex.Unlock();
1507 }
1508}
1509
1510//-------------------------------------------------------------------
1511// Open a chart from the stack with conditions
1512// a) Search Direction Start
1513// b) Requested Chart Type
1514//-------------------------------------------------------------------
1515
1516ChartBase *ChartDB::OpenStackChartConditional(
1517 ChartStack *ps, int index_start, bool bSearchDir, ChartTypeEnum New_Type,
1518 ChartFamilyEnum New_Family_Fallback) {
1519 int index;
1520
1521 int delta_index;
1522 ChartBase *ptc = NULL;
1523
1524 if (bSearchDir == 1)
1525 delta_index = -1;
1526
1527 else
1528 delta_index = 1;
1529
1530 index = index_start;
1531
1532 while ((index >= 0) && (index < ps->nEntry)) {
1533 ChartTypeEnum chart_type = (ChartTypeEnum)GetCSChartType(ps, index);
1534 if ((chart_type == New_Type) || (New_Type == CHART_TYPE_DONTCARE)) {
1535 ptc = OpenChartFromStack(ps, index);
1536 if (NULL != ptc) break;
1537 }
1538 index += delta_index;
1539 }
1540
1541 // Fallback, no useable chart of specified type found, so try for family
1542 // match
1543 if (NULL == ptc) {
1544 index = index_start;
1545
1546 while ((index >= 0) && (index < ps->nEntry)) {
1547 ChartFamilyEnum chart_family = GetCSChartFamily(ps, index);
1548 if (chart_family == New_Family_Fallback) {
1549 ptc = OpenChartFromStack(ps, index);
1550
1551 if (NULL != ptc) break;
1552 }
1553 index += delta_index;
1554 }
1555 }
1556
1557 return ptc;
1558}
1559
1560wxXmlDocument ChartDB::GetXMLDescription(int dbIndex, bool b_getGeom) {
1561 wxXmlDocument doc;
1562 if (!IsValid() || (dbIndex >= GetChartTableEntries())) return doc;
1563
1564 bool b_remove = !IsChartInCache(dbIndex);
1565
1566 wxXmlNode *pcell_node = NULL;
1567 wxXmlNode *node;
1568 wxXmlNode *tnode;
1569
1570 // Open the chart, without cacheing it
1571 ChartBase *pc = OpenChartFromDB(dbIndex, HEADER_ONLY);
1572 b_remove = !IsChartInCache(dbIndex);
1573 const ChartTableEntry &cte = GetChartTableEntry(dbIndex);
1574
1575 if (CHART_FAMILY_RASTER == (ChartFamilyEnum)cte.GetChartFamily()) {
1576 pcell_node = new wxXmlNode(wxXML_ELEMENT_NODE, "chart");
1577
1578 wxString path = GetDBChartFileName(dbIndex);
1579 node = new wxXmlNode(wxXML_ELEMENT_NODE, "path");
1580 pcell_node->AddChild(node);
1581 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", path);
1582 node->AddChild(tnode);
1583
1584 wxFileName name(path);
1585 node = new wxXmlNode(wxXML_ELEMENT_NODE, "name");
1586 pcell_node->AddChild(node);
1587 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", name.GetName());
1588 node->AddChild(tnode);
1589
1590 if (pc) {
1591 node = new wxXmlNode(wxXML_ELEMENT_NODE, "lname");
1592 pcell_node->AddChild(node);
1593 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pc->GetName());
1594 node->AddChild(tnode);
1595 }
1596
1597 wxString scale;
1598 scale.Printf("%d", cte.GetScale());
1599 node = new wxXmlNode(wxXML_ELEMENT_NODE, "cscale");
1600 pcell_node->AddChild(node);
1601 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", scale);
1602 node->AddChild(tnode);
1603
1604 wxDateTime file_date(cte.GetFileTime());
1605 file_date.MakeUTC();
1606 wxString sfile_date = file_date.FormatISODate();
1607 sfile_date += "T";
1608 sfile_date += file_date.FormatISOTime();
1609 sfile_date += "Z";
1610 node = new wxXmlNode(wxXML_ELEMENT_NODE, "local_file_datetime_iso8601");
1611 pcell_node->AddChild(node);
1612 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", sfile_date);
1613 node->AddChild(tnode);
1614
1615 if (pc) {
1616 node = new wxXmlNode(wxXML_ELEMENT_NODE, "source_edition");
1617 pcell_node->AddChild(node);
1618 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pc->GetSE());
1619 node->AddChild(tnode);
1620
1621 wxDateTime sdt = pc->GetEditionDate();
1622 wxString ssdt = "Unknown";
1623 if (sdt.IsValid()) ssdt = sdt.Format("%Y%m%d");
1624
1625 node = new wxXmlNode(wxXML_ELEMENT_NODE, "source_date");
1626 pcell_node->AddChild(node);
1627 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", ssdt);
1628 node->AddChild(tnode);
1629 }
1630
1631 /*
1632 if (s == "number")
1634 if (s == "raster_edition")
1635 if (s == "ntm_edition")
1637 if (s == "ntm_date")
1638 if (s == "source_edition_last_correction")
1639 if (s == "raster_edition_last_correction")
1640 if (s == "ntm_edition_last_correction")
1641 */
1642 }
1643
1644 else if (CHART_FAMILY_VECTOR == (ChartFamilyEnum)cte.GetChartFamily()) {
1645 pcell_node = new wxXmlNode(wxXML_ELEMENT_NODE, "cell");
1646
1647 wxString path = GetDBChartFileName(dbIndex);
1648 node = new wxXmlNode(wxXML_ELEMENT_NODE, "path");
1649 pcell_node->AddChild(node);
1650 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", path);
1651 node->AddChild(tnode);
1652
1653 wxFileName name(path);
1654 node = new wxXmlNode(wxXML_ELEMENT_NODE, "name");
1655 pcell_node->AddChild(node);
1656 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", name.GetName());
1657 node->AddChild(tnode);
1658
1659 wxString scale;
1660 scale.Printf("%d", cte.GetScale());
1661 node = new wxXmlNode(wxXML_ELEMENT_NODE, "cscale");
1662 pcell_node->AddChild(node);
1663 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", scale);
1664 node->AddChild(tnode);
1665
1666 wxDateTime file_date(cte.GetFileTime());
1667 file_date.MakeUTC();
1668 wxString sfile_date = file_date.FormatISODate();
1669 sfile_date += "T";
1670 sfile_date += file_date.FormatISOTime();
1671 sfile_date += "Z";
1672 node = new wxXmlNode(wxXML_ELEMENT_NODE, "local_file_datetime_iso8601");
1673 pcell_node->AddChild(node);
1674 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", sfile_date);
1675 node->AddChild(tnode);
1676
1677 if (pc) {
1678 node = new wxXmlNode(wxXML_ELEMENT_NODE, "edtn");
1679 pcell_node->AddChild(node);
1680 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pc->GetSE());
1681 node->AddChild(tnode);
1682 }
1683
1684 s57chart *pcs57 = dynamic_cast<s57chart *>(pc);
1685 if (pcs57) {
1686 node = new wxXmlNode(wxXML_ELEMENT_NODE, "isdt");
1687 pcell_node->AddChild(node);
1688 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pcs57->GetISDT());
1689 node->AddChild(tnode);
1690
1691 wxString LastUpdateDate;
1692 int updn =
1693 pcs57->ValidateAndCountUpdates(path, "", LastUpdateDate, false);
1694
1695 wxString supdn;
1696 supdn.Printf("%d", updn);
1697 node = new wxXmlNode(wxXML_ELEMENT_NODE, "updn");
1698 pcell_node->AddChild(node);
1699 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", supdn);
1700 node->AddChild(tnode);
1701
1702 node = new wxXmlNode(wxXML_ELEMENT_NODE, "uadt");
1703 pcell_node->AddChild(node);
1704 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", LastUpdateDate);
1705 node->AddChild(tnode);
1706 }
1707 }
1708
1709 if (pcell_node && b_getGeom) {
1710 node = new wxXmlNode(wxXML_ELEMENT_NODE, "cov");
1711 pcell_node->AddChild(node);
1712
1713 // Primary table
1714 if (cte.GetnPlyEntries()) {
1715 wxXmlNode *panelnode = new wxXmlNode(wxXML_ELEMENT_NODE, "panel");
1716 node->AddChild(panelnode);
1717
1718 wxString panel_no;
1719 panel_no.Printf("%d", 0);
1720 wxXmlNode *anode = new wxXmlNode(wxXML_TEXT_NODE, "", panel_no);
1721 panelnode->AddChild(anode);
1722
1723 float *pf = cte.GetpPlyTable();
1724 for (int j = 0; j < cte.GetnPlyEntries(); j++) {
1725 wxXmlNode *vnode = new wxXmlNode(wxXML_ELEMENT_NODE, "vertex");
1726 panelnode->AddChild(vnode);
1727
1728 wxXmlNode *latnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lat");
1729 vnode->AddChild(latnode);
1730
1731 float l = *pf++;
1732 wxString sl;
1733 sl.Printf("%.5f", l);
1734 wxXmlNode *vtnode = new wxXmlNode(wxXML_TEXT_NODE, "", sl);
1735 latnode->AddChild(vtnode);
1736
1737 wxXmlNode *lonnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lon");
1738 vnode->AddChild(lonnode);
1739
1740 float ll = *pf++;
1741 wxString sll;
1742 sll.Printf("%.5f", ll);
1743 wxXmlNode *vtlnode = new wxXmlNode(wxXML_TEXT_NODE, "", sll);
1744 lonnode->AddChild(vtlnode);
1745 }
1746 }
1747
1748 for (int i = 0; i < cte.GetnAuxPlyEntries(); i++) {
1749 wxXmlNode *panelnode = new wxXmlNode(wxXML_ELEMENT_NODE, "panel");
1750 node->AddChild(panelnode);
1751
1752 wxString panel_no;
1753 panel_no.Printf("%d", i + 1);
1754 wxXmlNode *anode = new wxXmlNode(wxXML_TEXT_NODE, "", panel_no);
1755 panelnode->AddChild(anode);
1756
1757 float *pf = cte.GetpAuxPlyTableEntry(i);
1758 for (int j = 0; j < cte.GetAuxCntTableEntry(i); j++) {
1759 wxXmlNode *vnode = new wxXmlNode(wxXML_ELEMENT_NODE, "vertex");
1760 panelnode->AddChild(vnode);
1761
1762 wxXmlNode *latnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lat");
1763 vnode->AddChild(latnode);
1764
1765 float l = *pf++;
1766 wxString sl;
1767 sl.Printf("%.5f", l);
1768 wxXmlNode *vtnode = new wxXmlNode(wxXML_TEXT_NODE, "", sl);
1769 latnode->AddChild(vtnode);
1770
1771 wxXmlNode *lonnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lon");
1772 vnode->AddChild(lonnode);
1773
1774 float ll = *pf++;
1775 wxString sll;
1776 sll.Printf("%.5f", ll);
1777 wxXmlNode *vtlnode = new wxXmlNode(wxXML_TEXT_NODE, "", sll);
1778 lonnode->AddChild(vtlnode);
1779 }
1780 }
1781 }
1782
1783 doc.SetRoot(pcell_node);
1784
1785 if (b_remove) DeleteCacheChart(pc);
1786
1787 return doc;
1788}
1789
1790bool ChartDB::CheckExclusiveTileGroup(int canvasIndex) {
1791 // Return true if the group active in the passed canvasIndex has only MBTiles
1792 // present Also, populate the persistent member variables, so that subsequent
1793 // checks are very fast.
1794
1795 // Get the chart canvas indexed by canvasIndex
1796 canvasConfig *cc;
1797 ChartCanvas *canvas = NULL;
1798 auto &config_array = ConfigMgr::Get().GetCanvasConfigArray();
1799
1800 switch (g_canvasConfig) {
1801 case 1:
1802 if (canvasIndex == 0) {
1803 cc = config_array.Item(0);
1804 if (cc) canvas = cc->canvas;
1805 } else {
1806 cc = config_array.Item(1);
1807 if (cc) canvas = cc->canvas;
1808 }
1809 break;
1810
1811 default:
1812 cc = config_array.Item(0);
1813 if (cc) canvas = cc->canvas;
1814 }
1815
1816 if (!canvas) return false;
1817
1818 // This canvas group index already checked?
1819 if (canvas->m_groupIndex == m_checkGroupIndex[canvasIndex])
1820 return m_checkedTileOnly[canvasIndex];
1821
1822 // Check the group for anything other than MBTiles...
1823 bool rv = IsNonMBTileInGroup(canvas->m_groupIndex);
1824
1825 m_checkGroupIndex[canvasIndex] = canvas->m_groupIndex;
1826 m_checkedTileOnly[canvasIndex] = !rv;
1827
1828 return !rv; // true iff group has only MBTiles
1829}
1830
1831bool ChartDB::CheckAnyCanvasExclusiveTileGroup() {
1832 // Check to determine if any canvas group is exclusively MBTiles
1833 // if so, return true;
1834
1835 bool rv = false;
1836
1837 canvasConfig *cc;
1838 ChartCanvas *canvas = NULL;
1839 auto &config_array = ConfigMgr::Get().GetCanvasConfigArray();
1840
1841 switch (g_canvasConfig) {
1842 case 1:
1843 cc = config_array.Item(0);
1844 if (cc) {
1845 ChartCanvas *canvas = cc->canvas;
1846 if (canvas) {
1847 if (canvas->m_groupIndex == m_checkGroupIndex[0])
1848 rv |= m_checkedTileOnly[0];
1849 }
1850 }
1851
1852 cc = config_array.Item(1);
1853 if (cc) {
1854 ChartCanvas *canvas = cc->canvas;
1855 if (canvas) {
1856 if (canvas->m_groupIndex == m_checkGroupIndex[1])
1857 rv |= m_checkedTileOnly[1];
1858 }
1859 }
1860 break;
1861
1862 default:
1863 cc = config_array.Item(0);
1864 if (cc) {
1865 ChartCanvas *canvas = cc->canvas;
1866 if (canvas) {
1867 if (canvas->m_groupIndex == m_checkGroupIndex[0])
1868 rv |= m_checkedTileOnly[0];
1869 }
1870 }
1871 }
1872
1873 return rv;
1874}
1875
1876// Private version of PolyPt testing using floats instead of doubles
1877
1878bool Intersect(MyFlPoint p1, MyFlPoint p2, MyFlPoint p3, MyFlPoint p4);
1879int CCW(MyFlPoint p0, MyFlPoint p1, MyFlPoint p2);
1880
1881/*************************************************************************
1882
1883
1884 * FUNCTION: G_FloatPtInPolygon
1885 *
1886 * PURPOSE
1887 * This routine determines if the point passed is in the polygon. It uses
1888
1889 * the classical polygon hit-testing algorithm: a horizontal ray starting
1890
1891 * at the point is extended infinitely rightwards and the number of
1892 * polygon edges that intersect the ray are counted. If the number is odd,
1893 * the point is inside the polygon.
1894 *
1895 * RETURN VALUE
1896 * (bool) TRUE if the point is inside the polygon, FALSE if not.
1897 *************************************************************************/
1898
1899bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y)
1900
1901{
1902 MyFlPoint *ppt, *ppt1;
1903 int i;
1904 MyFlPoint pt1, pt2, pt0;
1905 int wnumintsct = 0;
1906
1907 pt0.x = x;
1908 pt0.y = y;
1909
1910 pt1 = pt2 = pt0;
1911 pt2.x = 1.e6;
1912
1913 // Now go through each of the lines in the polygon and see if it
1914 // intersects
1915 for (i = 0, ppt = rgpts; i < wnumpts - 1; i++, ppt++) {
1916 ppt1 = ppt;
1917 ppt1++;
1918 if (Intersect(pt0, pt2, *ppt, *(ppt1))) wnumintsct++;
1919 }
1920
1921 // And the last line
1922 if (Intersect(pt0, pt2, *ppt, *rgpts)) wnumintsct++;
1923
1924 // return(wnumintsct&1);
1925
1926 // If result is false, check the degenerate case where test point lies
1927 // on a polygon endpoint
1928 if (!(wnumintsct & 1)) {
1929 for (i = 0, ppt = rgpts; i < wnumpts; i++, ppt++) {
1930 if (((*ppt).x == x) && ((*ppt).y == y)) return true;
1931 }
1932 } else
1933 return true;
1934
1935 return false;
1936}
1937
1938/*************************************************************************
1939
1940
1941 * FUNCTION: Intersect
1942 *
1943 * PURPOSE
1944 * Given two line segments, determine if they intersect.
1945 *
1946 * RETURN VALUE
1947 * TRUE if they intersect, FALSE if not.
1948 *************************************************************************/
1949
1950inline bool Intersect(MyFlPoint p1, MyFlPoint p2, MyFlPoint p3, MyFlPoint p4) {
1951 return (((CCW(p1, p2, p3) * CCW(p1, p2, p4)) <= 0) &&
1952 ((CCW(p3, p4, p1) * CCW(p3, p4, p2) <= 0)));
1953}
1954/*************************************************************************
1955
1956
1957 * FUNCTION: CCW (CounterClockWise)
1958 *
1959 * PURPOSE
1960 * Determines, given three points, if when travelling from the first to
1961 * the second to the third, we travel in a counterclockwise direction.
1962 *
1963 * RETURN VALUE
1964 * (int) 1 if the movement is in a counterclockwise direction, -1 if
1965 * not.
1966 *************************************************************************/
1967
1968inline int CCW(MyFlPoint p0, MyFlPoint p1, MyFlPoint p2) {
1969 float dx1, dx2;
1970 float dy1, dy2;
1971
1972 dx1 = p1.x - p0.x;
1973 dx2 = p2.x - p0.x;
1974 dy1 = p1.y - p0.y;
1975 dy2 = p2.y - p0.y;
1976
1977 /* This is basically a slope comparison: we don't do divisions because
1978
1979 * of divide by zero possibilities with pure horizontal and pure
1980 * vertical lines.
1981 */
1982 return ((dx1 * dy2 > dy1 * dx2) ? 1 : -1);
1983}
Basic platform specific support utilities without GUI deps.
bool GetMemoryStatus(int *mem_total, int *mem_used)
Return total system RAM and size of program Values returned are in kilobytes.
Chart canvas configuration state
std::vector< std::string > ChartDirectoryExcludedVector
Global instance.
Definition chartdb.cpp:73
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
std::vector< std::string > ChartDirectoryExcludedVector
Global instance.
Definition chartdb.cpp:73
ChartDB * ChartData
Global instance.
Definition chartdb.h:55
BSB chart management.
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
Generic Chart canvas base.
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:173
Manages the chart database and provides access to chart data.
Definition chartdb.h:95
bool LoadBinary(const wxString &filename, ArrayOfCDI &dir_array_check)
Load the chart database from a binary file.
Definition chartdb.cpp:242
Represents a KAP format chart, derived from ChartBaseBSB.
Definition chartimg.h:353
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
Encapsulates persistent canvas configuration.
ChartCanvas * canvas
Pointer to associated chart canvas.
Represents a single CM93 chart at a specific scale.
Definition cm93.h:300
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
Class cm93chart and helpers – CM93 chart state.
Config file user configuration interface.
OpenGL chart rendering canvas.
glTextureManager * g_glTextureManager
Global instance.
bool startswith(const std::string &str, const std::string &prefix)
Return true if s starts with given prefix.
Miscellaneous utilities, many of which string related.
S57 Chart Object.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:187
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.