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