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