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 LRUTime = pce->RecentTime;
1035 iOldest = i;
1036 }
1037 }
1038 }
1039 int dt = m_ticks - LRUTime;
1040
1041 CacheEntry *pce = (CacheEntry *)(pChartCache->Item(iOldest));
1042 ChartBase *pDeleteCandidate = (ChartBase *)(pce->pChart);
1043
1044 if (!pce->n_lock && !IsSingleChart(pDeleteCandidate)) {
1045 if (blog)
1046 wxLogMessage("Oldest unlocked cache index is %d, delta t is %d",
1047 iOldest, dt);
1048
1049 pret = pce;
1050 } else
1051 wxLogMessage("All chart in cache locked, size: %d", nCache);
1052 }
1053
1054 return pret;
1055}
1056
1057ChartBase *ChartDB::OpenChartUsingCache(int dbindex, ChartInitFlag init_flag) {
1058 if ((dbindex < 0) || (dbindex > GetChartTableEntries() - 1)) return NULL;
1059
1060 // printf("Opening chart %d lock: %d\n", dbindex, m_b_locked);
1061
1062 const ChartTableEntry &cte = GetChartTableEntry(dbindex);
1063 wxString ChartFullPath = cte.GetFullSystemPath();
1064 ChartTypeEnum chart_type = (ChartTypeEnum)cte.GetChartType();
1065 ChartFamilyEnum chart_family = (ChartFamilyEnum)cte.GetChartFamily();
1066
1067 wxString msg1;
1068 msg1.Printf("OpenChartUsingCache: type %d ", chart_type);
1069 // wxLogMessage(msg1 + ChartFullPath);
1070
1071 if (cte.GetLatMax() > 90.0) // Chart has been disabled...
1072 return NULL;
1073
1074 ChartBase *Ch = NULL;
1075 CacheEntry *pce = NULL;
1076 int old_lock = 0;
1077
1078 bool bInCache = false;
1079
1080 // Search the cache
1081 {
1082 wxMutexLocker lock(m_cache_mutex);
1083
1084 unsigned int nCache = pChartCache->GetCount();
1085 m_ticks++;
1086 for (unsigned int i = 0; i < nCache; i++) {
1087 pce = (CacheEntry *)(pChartCache->Item(i));
1088 if (pce->FullPath == ChartFullPath) {
1089 Ch = (ChartBase *)pce->pChart;
1090 bInCache = true;
1091 break;
1092 }
1093 }
1094
1095 if (bInCache) {
1096 wxString msg;
1097 msg.Printf("OpenChartUsingCache, IN cache: cache size: %d\n",
1098 (int)pChartCache->GetCount());
1099 // wxLogMessage(msg);
1100 if (FULL_INIT == init_flag) // asking for full init?
1101 {
1102 if (Ch->IsReadyToRender()) {
1103 if (pce) {
1104 pce->RecentTime = m_ticks; // chart is OK
1105 pce->b_in_use = true;
1106 }
1107 return Ch;
1108 } else {
1109 if (pthumbwin && pthumbwin->pThumbChart == Ch)
1110 pthumbwin->pThumbChart = NULL;
1111 delete Ch; // chart is not useable
1112 old_lock = pce->n_lock;
1113 pChartCache->Remove(pce); // so remove it
1114 delete pce;
1115
1116 bInCache = false;
1117 }
1118 } else // assume if in cache, the chart can do thumbnails
1119 {
1120 if (pce) {
1121 pce->RecentTime = m_ticks;
1122 pce->b_in_use = true;
1123 }
1124 return Ch;
1125 }
1126 }
1127
1128 if (!bInCache) // not in cache
1129 {
1130 SetBusy(true);
1131 if (!m_b_locked) {
1132 // Use memory limited cache policy, if defined....
1133 if (g_memCacheLimit) {
1134 // Check memory status to see if enough room to open another chart
1135 int mem_used;
1136 platform::GetMemoryStatus(0, &mem_used);
1137
1138 wxString msg;
1139 msg.Printf("OpenChartUsingCache, NOT in cache: cache size: %d\n",
1140 (int)pChartCache->GetCount());
1141 wxLogMessage(msg);
1142 wxString msg1;
1143 msg1.Printf(" OpenChartUsingCache: type %d ", chart_type);
1144 wxLogMessage(msg1 + ChartFullPath);
1145
1146 if ((mem_used > g_memCacheLimit * 8 / 10) &&
1147 (pChartCache->GetCount() > 2)) {
1148 wxString msg("Removing oldest chart from cache: ");
1149 while (1) {
1150 CacheEntry *pce = FindOldestDeleteCandidate(true);
1151 if (pce == 0) break; // no possible delete candidate
1152
1153 // purge texture cache, really need memory here
1154 DeleteCacheEntry(pce, true, msg);
1155
1156 platform::GetMemoryStatus(0, &mem_used);
1157 if ((mem_used < g_memCacheLimit * 8 / 10) ||
1158 (pChartCache->GetCount() <= 2))
1159 break;
1160
1161 } // while
1162 }
1163 }
1164
1165 else // Use n chart cache policy, if memory-limit policy is not used
1166 {
1167 // Limit cache to n charts, tossing out the oldest when space is
1168 // needed
1169 unsigned int nCache = pChartCache->GetCount();
1170 if (nCache > (unsigned int)g_nCacheLimit && nCache > 2) {
1171 wxString msg("Removing oldest chart from cache: ");
1172 while (nCache > (unsigned int)g_nCacheLimit) {
1173 CacheEntry *pce = FindOldestDeleteCandidate(true);
1174 if (pce == 0) break;
1175
1176 DeleteCacheEntry(pce, true, msg);
1177 nCache--;
1178 }
1179 }
1180 }
1181 }
1182 }
1183 } // unlock
1184
1185 if (!bInCache) // not in cache
1186 {
1187 wxLogMessage("Creating new chart");
1188
1189 if (chart_type == CHART_TYPE_KAP)
1190 Ch = new ChartKAP();
1191
1192 else if (chart_type == CHART_TYPE_GEO)
1193 Ch = new ChartGEO();
1194
1195 else if (chart_type == CHART_TYPE_MBTILES)
1196 Ch = new ChartMbTiles();
1197
1198 else if (chart_type == CHART_TYPE_S57) {
1199 LoadS57();
1200 Ch = new s57chart();
1201 s57chart *Chs57 = static_cast<s57chart *>(Ch);
1202
1203 Chs57->SetNativeScale(cte.GetScale());
1204
1205 // Explicitely set the chart extents from the database to
1206 // support the case wherein the SENC file has not yet been built
1207 Extent ext;
1208 ext.NLAT = cte.GetLatMax();
1209 ext.SLAT = cte.GetLatMin();
1210 ext.WLON = cte.GetLonMin();
1211 ext.ELON = cte.GetLonMax();
1212 Chs57->SetFullExtent(ext);
1213 }
1214
1215 else if (chart_type == CHART_TYPE_CM93) {
1216 LoadS57();
1217 Ch = new cm93chart();
1218 cm93chart *Chcm93 = static_cast<cm93chart *>(Ch);
1219
1220 Chcm93->SetNativeScale(cte.GetScale());
1221
1222 // Explicitely set the chart extents from the database to
1223 // support the case wherein the SENC file has not yet been built
1224 Extent ext;
1225 ext.NLAT = cte.GetLatMax();
1226 ext.SLAT = cte.GetLatMin();
1227 ext.WLON = cte.GetLonMin();
1228 ext.ELON = cte.GetLonMax();
1229 Chcm93->SetFullExtent(ext);
1230 }
1231
1232 else if (chart_type == CHART_TYPE_CM93COMP) {
1233 LoadS57();
1234 Ch = new cm93compchart();
1235
1236 cm93compchart *Chcm93 = static_cast<cm93compchart *>(Ch);
1237
1238 Chcm93->SetNativeScale(cte.GetScale());
1239
1240 // Explicitely set the chart extents from the database to
1241 // support the case wherein the SENC file has not yet been built
1242 Extent ext;
1243 ext.NLAT = cte.GetLatMax();
1244 ext.SLAT = cte.GetLatMin();
1245 ext.WLON = cte.GetLonMin();
1246 ext.ELON = cte.GetLonMax();
1247 Chcm93->SetFullExtent(ext);
1248 }
1249
1250 else if (chart_type == CHART_TYPE_PLUGIN) {
1251 wxFileName fn(ChartFullPath);
1252 wxString ext = fn.GetExt();
1253 ext.Prepend("*.");
1254 wxString ext_upper = ext.MakeUpper();
1255 wxString ext_lower = ext.MakeLower();
1256 wxString chart_class_name;
1257
1258 // Search the array of chart class descriptors to find a match
1259 // bewteen the search mask and the the chart file extension
1260
1261 for (auto &cd : m_ChartClassDescriptorArray) {
1262 if (cd.m_descriptor_type == PLUGIN_DESCRIPTOR) {
1263 if (cd.m_search_mask == ext_upper) {
1264 chart_class_name = cd.m_class_name;
1265 break;
1266 }
1267 if (cd.m_search_mask == ext_lower) {
1268 chart_class_name = cd.m_class_name;
1269 break;
1270 }
1271 if (ChartFullPath.Matches(cd.m_search_mask)) {
1272 chart_class_name = cd.m_class_name;
1273 break;
1274 }
1275 }
1276 }
1277
1278 // chart_class_name = cte.GetChartClassName();
1279 if (chart_class_name.Len()) {
1280 ChartPlugInWrapper *cpiw = new ChartPlugInWrapper(chart_class_name);
1281 Ch = (ChartBase *)cpiw;
1282 if (chart_family == CHART_FAMILY_VECTOR) LoadS57();
1283 }
1284 }
1285
1286 else {
1287 Ch = NULL;
1288 wxLogMessage("Unknown chart type");
1289 }
1290
1291 if (Ch) {
1292 InitReturn ir;
1293
1294 s52plib *plib = ps52plib;
1295 wxString msg_fn(ChartFullPath);
1296 msg_fn.Replace("%", "%%");
1297
1298 // Vector charts need a PLIB for useful display....
1299 if ((chart_family != CHART_FAMILY_VECTOR) ||
1300 ((chart_family == CHART_FAMILY_VECTOR) && plib)) {
1301 wxLogMessage(wxString::Format("Initializing Chart %s", msg_fn.c_str()));
1302
1303 ir = Ch->Init(ChartFullPath, init_flag); // using the passed flag
1304 Ch->SetColorScheme(user_colors::GetColorScheme());
1305 } else {
1306 wxLogMessage(wxString::Format(" No PLIB, Skipping vector chart %s",
1307 msg_fn.c_str()));
1308
1309 ir = INIT_FAIL_REMOVE;
1310 }
1311
1312 if (INIT_OK == ir) {
1313 // always cache after a new chart has been created
1314 // or it may leak CacheEntry in createthumbnail
1315 // if(FULL_INIT == init_flag)
1316 {
1317 pce = new CacheEntry;
1318 pce->FullPath = ChartFullPath;
1319 pce->pChart = Ch;
1320 pce->dbIndex = dbindex;
1321 // printf(" Adding chart %d\n",
1322 // dbindex);
1323 pce->RecentTime = m_ticks;
1324 pce->n_lock = old_lock;
1325
1326 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
1327 pChartCache->Add((void *)pce);
1328 m_cache_mutex.Unlock();
1329 } else {
1330 delete pce;
1331 }
1332 }
1333
1334 // A performance optimization.
1335 // Hide this chart's MBTiles overlay on initial MBTile chart load, or
1336 // reload after cache purge. This can help avoid excessively long
1337 // startup and group switch time when large tilesets are in use. See
1338 // FS#2601 Further optimization: If any chart group being shown
1339 // contains only MBTiles, and the target file is less than 5 GB in
1340 // size,
1341 // then allow immediate opening. Otherwise, add this chart to the
1342 // "no-show" array for each chart.
1343 if (chart_type == CHART_TYPE_MBTILES) {
1344 wxFileName tileFile(ChartFullPath);
1345 // Size test for 5 GByte
1346 wxULongLong tileSizeMB = tileFile.GetSize() >> 20;
1347
1348 auto &config_array = ConfigMgr::Get().GetCanvasConfigArray();
1349
1350 if (!CheckAnyCanvasExclusiveTileGroup() ||
1351 (tileSizeMB.GetLo() > 5000)) {
1352 // Check to see if the tile has been "clicked" in either canvas.
1353 // If so, do not add to no-show array again.
1354 bool b_clicked = false;
1355 canvasConfig *cc;
1356 ChartCanvas *canvas = NULL;
1357
1358 switch (g_canvasConfig) {
1359 case 1:
1360 cc = config_array.Item(0);
1361 if (cc) {
1362 ChartCanvas *canvas = cc->canvas;
1363 if (canvas)
1364 b_clicked |= canvas->IsTileOverlayIndexInYesShow(dbindex);
1365 }
1366 cc = config_array.Item(1);
1367 if (cc) {
1368 ChartCanvas *canvas = cc->canvas;
1369 if (canvas)
1370 b_clicked |= canvas->IsTileOverlayIndexInYesShow(dbindex);
1371 }
1372 break;
1373 default:
1374 cc = config_array.Item(0);
1375 if (cc) {
1376 ChartCanvas *canvas = cc->canvas;
1377 if (canvas)
1378 b_clicked |= canvas->IsTileOverlayIndexInYesShow(dbindex);
1379 }
1380 break;
1381 }
1382
1383 // Add to all canvas noshow arrays
1384 if (!b_clicked) {
1385 switch (g_canvasConfig) {
1386 case 1:
1387 cc = config_array.Item(0);
1388 if (cc) {
1389 ChartCanvas *canvas = cc->canvas;
1390 if (canvas) canvas->AddTileOverlayIndexToNoShow(dbindex);
1391 }
1392 cc = config_array.Item(1);
1393 if (cc) {
1394 ChartCanvas *canvas = cc->canvas;
1395 if (canvas) canvas->AddTileOverlayIndexToNoShow(dbindex);
1396 }
1397 break;
1398 default:
1399 cc = config_array.Item(0);
1400 if (cc) {
1401 ChartCanvas *canvas = cc->canvas;
1402 if (canvas) canvas->AddTileOverlayIndexToNoShow(dbindex);
1403 }
1404 break;
1405 }
1406 }
1407 }
1408 }
1409 } else if (INIT_FAIL_REMOVE == ir) // some problem in chart Init()
1410 {
1411 wxLogMessage(
1412 wxString::Format("Problem initializing Chart %s", msg_fn.c_str()));
1413
1414 delete Ch;
1415 Ch = NULL;
1416
1417 // Mark this chart in the database, so that it will not be seen
1418 // during this run, but will stay in the database
1419 DisableChart(ChartFullPath);
1420 } else if ((INIT_FAIL_RETRY == ir) ||
1421 (INIT_FAIL_NOERROR ==
1422 ir)) // recoverable problem in chart Init()
1423 {
1424 wxLogMessage(wxString::Format(
1425 "Recoverable problem initializing Chart %s", msg_fn.c_str()));
1426 delete Ch;
1427 Ch = NULL;
1428 }
1429
1430 if (INIT_OK != ir) {
1431 if (1 /*INIT_FAIL_NOERROR != ir*/) {
1432 wxLogMessage(
1433 wxString::Format(" OpenChartFromStack... Error opening "
1434 "chart %s ... return code %d",
1435 msg_fn.c_str(), ir));
1436 }
1437 }
1438 }
1439
1440 SetBusy(false);
1441
1442 return Ch;
1443 }
1444
1445 return NULL;
1446}
1447
1448//
1449bool ChartDB::DeleteCacheChart(ChartBase *pDeleteCandidate) {
1450 bool retval = false;
1451
1452 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
1453 if (!IsSingleChart(pDeleteCandidate)) {
1454 // Find the chart in the cache
1455 CacheEntry *pce = NULL;
1456 for (unsigned int i = 0; i < pChartCache->GetCount(); i++) {
1457 pce = (CacheEntry *)(pChartCache->Item(i));
1458 if ((ChartBase *)(pce->pChart) == pDeleteCandidate) {
1459 break;
1460 }
1461 }
1462
1463 if (pce) {
1464 if (pce->n_lock > 0) pce->n_lock--;
1465
1466 if (pce->n_lock == 0) {
1467 DeleteCacheEntry(pce);
1468 retval = true;
1469 }
1470 }
1471 }
1472 m_cache_mutex.Unlock();
1473 }
1474
1475 return retval;
1476}
1477
1478/*
1479 */
1480void ChartDB::ApplyColorSchemeToCachedCharts(ColorScheme cs) {
1481 ChartBase *Ch;
1482 CacheEntry *pce;
1483#ifdef ocpnUSE_GL
1484 // The background texture cache threads might be running
1485 if (g_glTextureManager) g_glTextureManager->PurgeJobList();
1486#endif
1487 // Search the cache
1488
1489 if (wxMUTEX_NO_ERROR == m_cache_mutex.Lock()) {
1490 unsigned int nCache = pChartCache->GetCount();
1491 for (unsigned int i = 0; i < nCache; i++) {
1492 pce = (CacheEntry *)(pChartCache->Item(i));
1493 Ch = (ChartBase *)pce->pChart;
1494 if (Ch) Ch->SetColorScheme(cs, true);
1495 }
1496
1497 m_cache_mutex.Unlock();
1498 }
1499}
1500
1501//-------------------------------------------------------------------
1502// Open a chart from the stack with conditions
1503// a) Search Direction Start
1504// b) Requested Chart Type
1505//-------------------------------------------------------------------
1506
1507ChartBase *ChartDB::OpenStackChartConditional(
1508 ChartStack *ps, int index_start, bool bSearchDir, ChartTypeEnum New_Type,
1509 ChartFamilyEnum New_Family_Fallback) {
1510 int index;
1511
1512 int delta_index;
1513 ChartBase *ptc = NULL;
1514
1515 if (bSearchDir == 1)
1516 delta_index = -1;
1517
1518 else
1519 delta_index = 1;
1520
1521 index = index_start;
1522
1523 while ((index >= 0) && (index < ps->nEntry)) {
1524 ChartTypeEnum chart_type = (ChartTypeEnum)GetCSChartType(ps, index);
1525 if ((chart_type == New_Type) || (New_Type == CHART_TYPE_DONTCARE)) {
1526 ptc = OpenChartFromStack(ps, index);
1527 if (NULL != ptc) break;
1528 }
1529 index += delta_index;
1530 }
1531
1532 // Fallback, no useable chart of specified type found, so try for family
1533 // match
1534 if (NULL == ptc) {
1535 index = index_start;
1536
1537 while ((index >= 0) && (index < ps->nEntry)) {
1538 ChartFamilyEnum chart_family = GetCSChartFamily(ps, index);
1539 if (chart_family == New_Family_Fallback) {
1540 ptc = OpenChartFromStack(ps, index);
1541
1542 if (NULL != ptc) break;
1543 }
1544 index += delta_index;
1545 }
1546 }
1547
1548 return ptc;
1549}
1550
1551wxXmlDocument ChartDB::GetXMLDescription(int dbIndex, bool b_getGeom) {
1552 wxXmlDocument doc;
1553 if (!IsValid() || (dbIndex >= GetChartTableEntries())) return doc;
1554
1555 bool b_remove = !IsChartInCache(dbIndex);
1556
1557 wxXmlNode *pcell_node = NULL;
1558 wxXmlNode *node;
1559 wxXmlNode *tnode;
1560
1561 // Open the chart, without cacheing it
1562 ChartBase *pc = OpenChartFromDB(dbIndex, HEADER_ONLY);
1563 b_remove = !IsChartInCache(dbIndex);
1564 const ChartTableEntry &cte = GetChartTableEntry(dbIndex);
1565
1566 if (CHART_FAMILY_RASTER == (ChartFamilyEnum)cte.GetChartFamily()) {
1567 pcell_node = new wxXmlNode(wxXML_ELEMENT_NODE, "chart");
1568
1569 wxString path = GetDBChartFileName(dbIndex);
1570 node = new wxXmlNode(wxXML_ELEMENT_NODE, "path");
1571 pcell_node->AddChild(node);
1572 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", path);
1573 node->AddChild(tnode);
1574
1575 wxFileName name(path);
1576 node = new wxXmlNode(wxXML_ELEMENT_NODE, "name");
1577 pcell_node->AddChild(node);
1578 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", name.GetName());
1579 node->AddChild(tnode);
1580
1581 if (pc) {
1582 node = new wxXmlNode(wxXML_ELEMENT_NODE, "lname");
1583 pcell_node->AddChild(node);
1584 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pc->GetName());
1585 node->AddChild(tnode);
1586 }
1587
1588 wxString scale;
1589 scale.Printf("%d", cte.GetScale());
1590 node = new wxXmlNode(wxXML_ELEMENT_NODE, "cscale");
1591 pcell_node->AddChild(node);
1592 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", scale);
1593 node->AddChild(tnode);
1594
1595 wxDateTime file_date(cte.GetFileTime());
1596 file_date.MakeUTC();
1597 wxString sfile_date = file_date.FormatISODate();
1598 sfile_date += "T";
1599 sfile_date += file_date.FormatISOTime();
1600 sfile_date += "Z";
1601 node = new wxXmlNode(wxXML_ELEMENT_NODE, "local_file_datetime_iso8601");
1602 pcell_node->AddChild(node);
1603 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", sfile_date);
1604 node->AddChild(tnode);
1605
1606 if (pc) {
1607 node = new wxXmlNode(wxXML_ELEMENT_NODE, "source_edition");
1608 pcell_node->AddChild(node);
1609 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pc->GetSE());
1610 node->AddChild(tnode);
1611
1612 wxDateTime sdt = pc->GetEditionDate();
1613 wxString ssdt = "Unknown";
1614 if (sdt.IsValid()) ssdt = sdt.Format("%Y%m%d");
1615
1616 node = new wxXmlNode(wxXML_ELEMENT_NODE, "source_date");
1617 pcell_node->AddChild(node);
1618 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", ssdt);
1619 node->AddChild(tnode);
1620 }
1621
1622 /*
1623 if (s == "number")
1625 if (s == "raster_edition")
1626 if (s == "ntm_edition")
1628 if (s == "ntm_date")
1629 if (s == "source_edition_last_correction")
1630 if (s == "raster_edition_last_correction")
1631 if (s == "ntm_edition_last_correction")
1632 */
1633 }
1634
1635 else if (CHART_FAMILY_VECTOR == (ChartFamilyEnum)cte.GetChartFamily()) {
1636 pcell_node = new wxXmlNode(wxXML_ELEMENT_NODE, "cell");
1637
1638 wxString path = GetDBChartFileName(dbIndex);
1639 node = new wxXmlNode(wxXML_ELEMENT_NODE, "path");
1640 pcell_node->AddChild(node);
1641 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", path);
1642 node->AddChild(tnode);
1643
1644 wxFileName name(path);
1645 node = new wxXmlNode(wxXML_ELEMENT_NODE, "name");
1646 pcell_node->AddChild(node);
1647 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", name.GetName());
1648 node->AddChild(tnode);
1649
1650 wxString scale;
1651 scale.Printf("%d", cte.GetScale());
1652 node = new wxXmlNode(wxXML_ELEMENT_NODE, "cscale");
1653 pcell_node->AddChild(node);
1654 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", scale);
1655 node->AddChild(tnode);
1656
1657 wxDateTime file_date(cte.GetFileTime());
1658 file_date.MakeUTC();
1659 wxString sfile_date = file_date.FormatISODate();
1660 sfile_date += "T";
1661 sfile_date += file_date.FormatISOTime();
1662 sfile_date += "Z";
1663 node = new wxXmlNode(wxXML_ELEMENT_NODE, "local_file_datetime_iso8601");
1664 pcell_node->AddChild(node);
1665 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", sfile_date);
1666 node->AddChild(tnode);
1667
1668 if (pc) {
1669 node = new wxXmlNode(wxXML_ELEMENT_NODE, "edtn");
1670 pcell_node->AddChild(node);
1671 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pc->GetSE());
1672 node->AddChild(tnode);
1673 }
1674
1675 s57chart *pcs57 = dynamic_cast<s57chart *>(pc);
1676 if (pcs57) {
1677 node = new wxXmlNode(wxXML_ELEMENT_NODE, "isdt");
1678 pcell_node->AddChild(node);
1679 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", pcs57->GetISDT());
1680 node->AddChild(tnode);
1681
1682 wxString LastUpdateDate;
1683 int updn =
1684 pcs57->ValidateAndCountUpdates(path, "", LastUpdateDate, false);
1685
1686 wxString supdn;
1687 supdn.Printf("%d", updn);
1688 node = new wxXmlNode(wxXML_ELEMENT_NODE, "updn");
1689 pcell_node->AddChild(node);
1690 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", supdn);
1691 node->AddChild(tnode);
1692
1693 node = new wxXmlNode(wxXML_ELEMENT_NODE, "uadt");
1694 pcell_node->AddChild(node);
1695 tnode = new wxXmlNode(wxXML_TEXT_NODE, "", LastUpdateDate);
1696 node->AddChild(tnode);
1697 }
1698 }
1699
1700 if (pcell_node && b_getGeom) {
1701 node = new wxXmlNode(wxXML_ELEMENT_NODE, "cov");
1702 pcell_node->AddChild(node);
1703
1704 // Primary table
1705 if (cte.GetnPlyEntries()) {
1706 wxXmlNode *panelnode = new wxXmlNode(wxXML_ELEMENT_NODE, "panel");
1707 node->AddChild(panelnode);
1708
1709 wxString panel_no;
1710 panel_no.Printf("%d", 0);
1711 wxXmlNode *anode = new wxXmlNode(wxXML_TEXT_NODE, "", panel_no);
1712 panelnode->AddChild(anode);
1713
1714 float *pf = cte.GetpPlyTable();
1715 for (int j = 0; j < cte.GetnPlyEntries(); j++) {
1716 wxXmlNode *vnode = new wxXmlNode(wxXML_ELEMENT_NODE, "vertex");
1717 panelnode->AddChild(vnode);
1718
1719 wxXmlNode *latnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lat");
1720 vnode->AddChild(latnode);
1721
1722 float l = *pf++;
1723 wxString sl;
1724 sl.Printf("%.5f", l);
1725 wxXmlNode *vtnode = new wxXmlNode(wxXML_TEXT_NODE, "", sl);
1726 latnode->AddChild(vtnode);
1727
1728 wxXmlNode *lonnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lon");
1729 vnode->AddChild(lonnode);
1730
1731 float ll = *pf++;
1732 wxString sll;
1733 sll.Printf("%.5f", ll);
1734 wxXmlNode *vtlnode = new wxXmlNode(wxXML_TEXT_NODE, "", sll);
1735 lonnode->AddChild(vtlnode);
1736 }
1737 }
1738
1739 for (int i = 0; i < cte.GetnAuxPlyEntries(); i++) {
1740 wxXmlNode *panelnode = new wxXmlNode(wxXML_ELEMENT_NODE, "panel");
1741 node->AddChild(panelnode);
1742
1743 wxString panel_no;
1744 panel_no.Printf("%d", i + 1);
1745 wxXmlNode *anode = new wxXmlNode(wxXML_TEXT_NODE, "", panel_no);
1746 panelnode->AddChild(anode);
1747
1748 float *pf = cte.GetpAuxPlyTableEntry(i);
1749 for (int j = 0; j < cte.GetAuxCntTableEntry(i); j++) {
1750 wxXmlNode *vnode = new wxXmlNode(wxXML_ELEMENT_NODE, "vertex");
1751 panelnode->AddChild(vnode);
1752
1753 wxXmlNode *latnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lat");
1754 vnode->AddChild(latnode);
1755
1756 float l = *pf++;
1757 wxString sl;
1758 sl.Printf("%.5f", l);
1759 wxXmlNode *vtnode = new wxXmlNode(wxXML_TEXT_NODE, "", sl);
1760 latnode->AddChild(vtnode);
1761
1762 wxXmlNode *lonnode = new wxXmlNode(wxXML_ELEMENT_NODE, "lon");
1763 vnode->AddChild(lonnode);
1764
1765 float ll = *pf++;
1766 wxString sll;
1767 sll.Printf("%.5f", ll);
1768 wxXmlNode *vtlnode = new wxXmlNode(wxXML_TEXT_NODE, "", sll);
1769 lonnode->AddChild(vtlnode);
1770 }
1771 }
1772 }
1773
1774 doc.SetRoot(pcell_node);
1775
1776 if (b_remove) DeleteCacheChart(pc);
1777
1778 return doc;
1779}
1780
1781bool ChartDB::CheckExclusiveTileGroup(int canvasIndex) {
1782 // Return true if the group active in the passed canvasIndex has only MBTiles
1783 // present Also, populate the persistent member variables, so that subsequent
1784 // checks are very fast.
1785
1786 // Get the chart canvas indexed by canvasIndex
1787 canvasConfig *cc;
1788 ChartCanvas *canvas = NULL;
1789 auto &config_array = ConfigMgr::Get().GetCanvasConfigArray();
1790
1791 switch (g_canvasConfig) {
1792 case 1:
1793 if (canvasIndex == 0) {
1794 cc = config_array.Item(0);
1795 if (cc) canvas = cc->canvas;
1796 } else {
1797 cc = config_array.Item(1);
1798 if (cc) canvas = cc->canvas;
1799 }
1800 break;
1801
1802 default:
1803 cc = config_array.Item(0);
1804 if (cc) canvas = cc->canvas;
1805 }
1806
1807 if (!canvas) return false;
1808
1809 // This canvas group index already checked?
1810 if (canvas->m_groupIndex == m_checkGroupIndex[canvasIndex])
1811 return m_checkedTileOnly[canvasIndex];
1812
1813 // Check the group for anything other than MBTiles...
1814 bool rv = IsNonMBTileInGroup(canvas->m_groupIndex);
1815
1816 m_checkGroupIndex[canvasIndex] = canvas->m_groupIndex;
1817 m_checkedTileOnly[canvasIndex] = !rv;
1818
1819 return !rv; // true iff group has only MBTiles
1820}
1821
1822bool ChartDB::CheckAnyCanvasExclusiveTileGroup() {
1823 // Check to determine if any canvas group is exclusively MBTiles
1824 // if so, return true;
1825
1826 bool rv = false;
1827
1828 canvasConfig *cc;
1829 ChartCanvas *canvas = NULL;
1830 auto &config_array = ConfigMgr::Get().GetCanvasConfigArray();
1831
1832 switch (g_canvasConfig) {
1833 case 1:
1834 cc = config_array.Item(0);
1835 if (cc) {
1836 ChartCanvas *canvas = cc->canvas;
1837 if (canvas) {
1838 if (canvas->m_groupIndex == m_checkGroupIndex[0])
1839 rv |= m_checkedTileOnly[0];
1840 }
1841 }
1842
1843 cc = config_array.Item(1);
1844 if (cc) {
1845 ChartCanvas *canvas = cc->canvas;
1846 if (canvas) {
1847 if (canvas->m_groupIndex == m_checkGroupIndex[1])
1848 rv |= m_checkedTileOnly[1];
1849 }
1850 }
1851 break;
1852
1853 default:
1854 cc = config_array.Item(0);
1855 if (cc) {
1856 ChartCanvas *canvas = cc->canvas;
1857 if (canvas) {
1858 if (canvas->m_groupIndex == m_checkGroupIndex[0])
1859 rv |= m_checkedTileOnly[0];
1860 }
1861 }
1862 }
1863
1864 return rv;
1865}
1866
1867// Private version of PolyPt testing using floats instead of doubles
1868
1869bool Intersect(MyFlPoint p1, MyFlPoint p2, MyFlPoint p3, MyFlPoint p4);
1870int CCW(MyFlPoint p0, MyFlPoint p1, MyFlPoint p2);
1871
1872/*************************************************************************
1873
1874
1875 * FUNCTION: G_FloatPtInPolygon
1876 *
1877 * PURPOSE
1878 * This routine determines if the point passed is in the polygon. It uses
1879
1880 * the classical polygon hit-testing algorithm: a horizontal ray starting
1881
1882 * at the point is extended infinitely rightwards and the number of
1883 * polygon edges that intersect the ray are counted. If the number is odd,
1884 * the point is inside the polygon.
1885 *
1886 * RETURN VALUE
1887 * (bool) TRUE if the point is inside the polygon, FALSE if not.
1888 *************************************************************************/
1889
1890bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y)
1891
1892{
1893 MyFlPoint *ppt, *ppt1;
1894 int i;
1895 MyFlPoint pt1, pt2, pt0;
1896 int wnumintsct = 0;
1897
1898 pt0.x = x;
1899 pt0.y = y;
1900
1901 pt1 = pt2 = pt0;
1902 pt2.x = 1.e6;
1903
1904 // Now go through each of the lines in the polygon and see if it
1905 // intersects
1906 for (i = 0, ppt = rgpts; i < wnumpts - 1; i++, ppt++) {
1907 ppt1 = ppt;
1908 ppt1++;
1909 if (Intersect(pt0, pt2, *ppt, *(ppt1))) wnumintsct++;
1910 }
1911
1912 // And the last line
1913 if (Intersect(pt0, pt2, *ppt, *rgpts)) wnumintsct++;
1914
1915 // return(wnumintsct&1);
1916
1917 // If result is false, check the degenerate case where test point lies
1918 // on a polygon endpoint
1919 if (!(wnumintsct & 1)) {
1920 for (i = 0, ppt = rgpts; i < wnumpts; i++, ppt++) {
1921 if (((*ppt).x == x) && ((*ppt).y == y)) return true;
1922 }
1923 } else
1924 return true;
1925
1926 return false;
1927}
1928
1929/*************************************************************************
1930
1931
1932 * FUNCTION: Intersect
1933 *
1934 * PURPOSE
1935 * Given two line segments, determine if they intersect.
1936 *
1937 * RETURN VALUE
1938 * TRUE if they intersect, FALSE if not.
1939 *************************************************************************/
1940
1941inline bool Intersect(MyFlPoint p1, MyFlPoint p2, MyFlPoint p3, MyFlPoint p4) {
1942 return (((CCW(p1, p2, p3) * CCW(p1, p2, p4)) <= 0) &&
1943 ((CCW(p3, p4, p1) * CCW(p3, p4, p2) <= 0)));
1944}
1945/*************************************************************************
1946
1947
1948 * FUNCTION: CCW (CounterClockWise)
1949 *
1950 * PURPOSE
1951 * Determines, given three points, if when travelling from the first to
1952 * the second to the third, we travel in a counterclockwise direction.
1953 *
1954 * RETURN VALUE
1955 * (int) 1 if the movement is in a counterclockwise direction, -1 if
1956 * not.
1957 *************************************************************************/
1958
1959inline int CCW(MyFlPoint p0, MyFlPoint p1, MyFlPoint p2) {
1960 float dx1, dx2;
1961 float dy1, dy2;
1962
1963 dx1 = p1.x - p0.x;
1964 dx2 = p2.x - p0.x;
1965 dy1 = p1.y - p0.y;
1966 dy2 = p2.y - p0.y;
1967
1968 /* This is basically a slope comparison: we don't do divisions because
1969
1970 * of divide by zero possibilities with pure horizontal and pure
1971 * vertical lines.
1972 */
1973 return ((dx1 * dy2 > dy1 * dx2) ? 1 : -1);
1974}
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.