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