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