OpenCPN Partial API docs
chartdbs.cpp
1 /**************************************************************************
2  *
3  * Project: ChartManager
4  * Purpose: Basic Chart Info Storage
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 #include <wx/wxprec.h>
27 
28 #ifndef WX_PRECOMP
29 #include <wx/wx.h>
30 #endif
31 
32 #include <wx/arrimpl.cpp>
33 #include <wx/encconv.h>
34 #include <wx/regex.h>
35 #include <wx/progdlg.h>
36 #include <wx/tokenzr.h>
37 #include <wx/dir.h>
38 
39 #include "chartdbs.h"
40 #include "chartbase.h"
41 #include "pluginmanager.h"
42 #include "mbtiles.h"
43 #include "mygeom.h" // For DouglasPeucker();
44 #include "FlexHash.h"
45 #include "LOD_reduce.h"
46 #include "shapefile_basemap.h"
47 
48 #ifndef UINT32
49 #define UINT32 unsigned int
50 #endif
51 
52 #ifdef __OCPN__ANDROID__
53 #include "androidUTIL.h"
54 #endif
55 
56 extern PlugInManager *g_pi_manager;
57 extern wxString gWorldMapLocation;
58 extern wxString gWorldShapefileLocation;
59 extern ShapeBaseChartSet gShapeBasemap;
60 
61 static int s_dbVersion; // Database version currently in use at runtime
62  // Needed for ChartTableEntry::GetChartType() only
63  // TODO This can go away at opencpn Version 1.3.8 and
64  // above....
66 
67 bool FindMatchingFile(const wxString &theDir, const wxChar *theRegEx,
68  int nameLength, wxString &theMatch) {
69  wxDir dir(theDir);
70  wxRegEx rePattern(theRegEx);
71  for (bool fileFound = dir.GetFirst(&theMatch); fileFound;
72  fileFound = dir.GetNext(&theMatch))
73  if (theMatch.length() == (unsigned int)nameLength &&
74  rePattern.Matches(theMatch))
75  return true;
76  return false;
77 }
78 
79 static ChartFamilyEnum GetChartFamily(int charttype) {
80  ChartFamilyEnum cf;
81 
82  switch (charttype) {
83  case CHART_TYPE_KAP:
84  cf = CHART_FAMILY_RASTER;
85  break;
86  case CHART_TYPE_GEO:
87  cf = CHART_FAMILY_RASTER;
88  break;
89  case CHART_TYPE_S57:
90  cf = CHART_FAMILY_VECTOR;
91  break;
92  case CHART_TYPE_CM93:
93  cf = CHART_FAMILY_VECTOR;
94  break;
95  case CHART_TYPE_CM93COMP:
96  cf = CHART_FAMILY_VECTOR;
97  break;
98  case CHART_TYPE_DUMMY:
99  cf = CHART_FAMILY_RASTER;
100  break;
101  case CHART_TYPE_UNKNOWN:
102  cf = CHART_FAMILY_UNKNOWN;
103  break;
104  default:
105  cf = CHART_FAMILY_UNKNOWN;
106  break;
107  }
108  return cf;
109 }
110 
112 // ChartTableHeader
114 
115 void ChartTableHeader::Read(wxInputStream &is) {
116  is.Read(this, sizeof(ChartTableHeader));
117 }
118 
119 void ChartTableHeader::Write(wxOutputStream &os) {
120  char vb[5];
121  sprintf(vb, "V%03d", DB_VERSION_CURRENT);
122 
123  memcpy(dbVersion, vb, 4);
124  os.Write(this, sizeof(ChartTableHeader));
125 }
126 
127 bool ChartTableHeader::CheckValid() {
128  char vb[5];
129  sprintf(vb, "V%03d", DB_VERSION_CURRENT);
130  if (strncmp(vb, dbVersion, sizeof(dbVersion))) {
131  wxString msg;
132  char vbo[5];
133  memcpy(vbo, dbVersion, 4);
134  vbo[4] = 0;
135  msg.Append(wxString(vbo, wxConvUTF8));
136  msg.Prepend(wxT(" Warning: found incorrect chart db version: "));
137  wxLogMessage(msg);
138 
139  // return false; // no match....
140 
141  // Try previous version....
142  sprintf(vb, "V%03d", DB_VERSION_PREVIOUS);
143  if (strncmp(vb, dbVersion, sizeof(dbVersion)))
144  return false;
145  else {
146  wxLogMessage(
147  _T(" Scheduling db upgrade to current db version on ")
148  _T("Options->Charts page visit..."));
149  return true;
150  }
151 
152  } else {
153  wxString msg;
154  char vbo[5];
155  memcpy(vbo, dbVersion, 4);
156  vbo[4] = 0;
157  msg.Append(wxString(vbo, wxConvUTF8));
158  msg.Prepend(wxT("Loading chart db version: "));
159  wxLogMessage(msg);
160  }
161 
162  return true;
163 }
164 
166 // ChartTableEntry
168 
169 void ChartTableEntry::SetScale(int scale) {
170  Scale = scale;
171  rounding = 0;
172  // XXX find the right rounding
173  if (Scale >= 1000) rounding = 5 * pow(10, log10(Scale) - 2);
174 }
175 
176 ChartTableEntry::ChartTableEntry(ChartBase &theChart, wxString &utf8Path) {
177  Clear();
178 
179  char *pt = (char *)malloc(strlen(utf8Path.mb_str(wxConvUTF8)) + 1);
180  strcpy(pt, utf8Path.mb_str(wxConvUTF8));
181  pFullPath = pt;
182 
183  SetScale(theChart.GetNativeScale());
184 
185  ChartType = theChart.GetChartType();
186  ChartFamily = theChart.GetChartFamily();
187 
188  Skew = theChart.GetChartSkew();
189  ProjectionType = theChart.GetChartProjectionType();
190 
191  wxDateTime ed = theChart.GetEditionDate();
192  if (theChart.GetEditionDate().IsValid())
193  edition_date = theChart.GetEditionDate().GetTicks();
194 
195  wxFileName fn(theChart.GetFullPath());
196  if (fn.GetModificationTime().IsValid())
197  file_date = fn.GetModificationTime().GetTicks();
198 
199  m_pfilename = new wxString; // create and populate helper members
200  *m_pfilename = fn.GetFullName();
201  m_psFullPath = new wxString;
202  *m_psFullPath = utf8Path;
203  m_fullSystemPath = utf8Path;
204 
205 #ifdef __OCPN__ANDROID__
206  m_fullSystemPath = wxString(utf8Path.mb_str(wxConvUTF8));
207 #endif
208 
209  Extent ext;
210  theChart.GetChartExtent(&ext);
211  LatMax = ext.NLAT;
212  LatMin = ext.SLAT;
213  LonMin = ext.WLON;
214  LonMax = ext.ELON;
215 
216  m_bbox.Set(LatMin, LonMin, LatMax, LonMax);
217 
218  // Fill in the PLY information
219  // LOD calculation
220  int LOD_pixels = 1;
221  double scale_max_zoom = Scale / 4;
222 
223  double display_ppm = 1 / .00025; // nominal for most LCD displays
224  double meters_per_pixel_max_scale = scale_max_zoom / display_ppm;
225  double LOD_meters = meters_per_pixel_max_scale * LOD_pixels;
226 
227  // double LOD_meters = 5;
228 
229  // If COVR table has only one entry, us it for the primary Ply Table
230  if (theChart.GetCOVREntries() == 1) {
231  nPlyEntries = theChart.GetCOVRTablePoints(0);
232 
233  if (nPlyEntries > 5 && (LOD_meters > .01)) {
234  std::vector<int> index_keep{0, nPlyEntries - 1, 1, nPlyEntries - 2};
235 
236  double *DPbuffer = (double *)malloc(2 * nPlyEntries * sizeof(double));
237 
238  double *pfed = DPbuffer;
239  Plypoint *ppp = (Plypoint *)theChart.GetCOVRTableHead(0);
240 
241  for (int i = 0; i < nPlyEntries; i++) {
242  *pfed++ = ppp->ltp;
243  *pfed++ = ppp->lnp;
244  ppp++;
245  }
246 
247  DouglasPeucker(DPbuffer, 1, nPlyEntries - 2, LOD_meters / (1852 * 60),
248  &index_keep);
249  // printf("DB DP Reduction: %d/%d\n", index_keep.size(),
250  // nPlyEntries);
251 
252  // Mark the keepers by adding a simple constant to ltp
253  for (unsigned int i = 0; i < index_keep.size(); i++) {
254  DPbuffer[2 * index_keep[i]] += 2000.;
255  }
256 
257  float *pf = (float *)malloc(2 * index_keep.size() * sizeof(float));
258  float *pfe = pf;
259 
260  for (int i = 0; i < nPlyEntries; i++) {
261  if (DPbuffer[2 * i] > 1000.) {
262  *pfe++ = DPbuffer[2 * i] - 2000.;
263  *pfe++ = DPbuffer[(2 * i) + 1];
264  }
265  }
266 
267  pPlyTable = pf;
268  nPlyEntries = index_keep.size();
269  free(DPbuffer);
270  } else {
271  float *pf = (float *)malloc(2 * nPlyEntries * sizeof(float));
272  pPlyTable = pf;
273  float *pfe = pf;
274  Plypoint *ppp = (Plypoint *)theChart.GetCOVRTableHead(0);
275 
276  for (int i = 0; i < nPlyEntries; i++) {
277  *pfe++ = ppp->ltp;
278  *pfe++ = ppp->lnp;
279  ppp++;
280  }
281  }
282  }
283  // Else create a rectangular primary Ply Table from the chart extents
284  // and create AuxPly table from the COVR tables
285  else {
286  // Create new artificial Ply table from chart extents
287  nPlyEntries = 4;
288  float *pf1 = (float *)malloc(2 * 4 * sizeof(float));
289  pPlyTable = pf1;
290  float *pfe = pf1;
291  Extent fext;
292  theChart.GetChartExtent(&fext);
293 
294  *pfe++ = fext.NLAT; // LatMax;
295  *pfe++ = fext.WLON; // LonMin;
296 
297  *pfe++ = fext.NLAT; // LatMax;
298  *pfe++ = fext.ELON; // LonMax;
299 
300  *pfe++ = fext.SLAT; // LatMin;
301  *pfe++ = fext.ELON; // LonMax;
302 
303  *pfe++ = fext.SLAT; // LatMin;
304  *pfe++ = fext.WLON; // LonMin;
305 
306  // Fill in the structure for pAuxPlyTable
307 
308  nAuxPlyEntries = theChart.GetCOVREntries();
309  wxASSERT(nAuxPlyEntries);
310  float **pfp = (float **)malloc(nAuxPlyEntries * sizeof(float *));
311  float **pft0 = pfp;
312  int *pip = (int *)malloc(nAuxPlyEntries * sizeof(int));
313 
314  for (int j = 0; j < nAuxPlyEntries; j++) {
315  int nPE = theChart.GetCOVRTablePoints(j);
316 
317  if (nPE > 5 && (LOD_meters > .01)) {
318  std::vector<int> index_keep{0, nPE - 1, 1, nPE - 2};
319 
320  double *DPbuffer = (double *)malloc(2 * nPE * sizeof(double));
321 
322  double *pfed = DPbuffer;
323  Plypoint *ppp = (Plypoint *)theChart.GetCOVRTableHead(j);
324 
325  for (int i = 0; i < nPE; i++) {
326  *pfed++ = ppp->ltp;
327  *pfed++ = ppp->lnp;
328  ppp++;
329  }
330 
331  DouglasPeucker(DPbuffer, 1, nPE - 2, LOD_meters / (1852 * 60),
332  &index_keep);
333  // printf("DBa DP Reduction: %d/%d\n",
334  // index_keep.size(), nPE);
335 
336  // Mark the keepers by adding a simple constant to ltp
337  for (unsigned int i = 0; i < index_keep.size(); i++) {
338  DPbuffer[2 * index_keep[i]] += 2000.;
339  }
340 
341  float *pf = (float *)malloc(2 * index_keep.size() * sizeof(float));
342  float *pfe = pf;
343 
344  for (int i = 0; i < nPE; i++) {
345  if (DPbuffer[2 * i] > 1000.) {
346  *pfe++ = DPbuffer[2 * i] - 2000.;
347  *pfe++ = DPbuffer[(2 * i) + 1];
348  }
349  }
350 
351  pft0[j] = pf;
352  pip[j] = index_keep.size();
353  free(DPbuffer);
354  } else {
355  float *pf_entry =
356  (float *)malloc(theChart.GetCOVRTablePoints(j) * 2 * sizeof(float));
357  memcpy(pf_entry, theChart.GetCOVRTableHead(j),
358  theChart.GetCOVRTablePoints(j) * 2 * sizeof(float));
359  pft0[j] = pf_entry;
360  pip[j] = theChart.GetCOVRTablePoints(j);
361  }
362  }
363 
364  pAuxPlyTable = pfp;
365  pAuxCntTable = pip;
366  }
367 
368  // Get and populate the NoCovr tables
369 
370  nNoCovrPlyEntries = theChart.GetNoCOVREntries();
371  if (nNoCovrPlyEntries == 0) return;
372 
373  float **pfpnc = (float **)malloc(nNoCovrPlyEntries * sizeof(float *));
374  float **pft0nc = pfpnc;
375  int *pipnc = (int *)malloc(nNoCovrPlyEntries * sizeof(int));
376 
377  for (int j = 0; j < nNoCovrPlyEntries; j++) {
378  float *pf_entry =
379  (float *)malloc(theChart.GetNoCOVRTablePoints(j) * 2 * sizeof(float));
380  memcpy(pf_entry, theChart.GetNoCOVRTableHead(j),
381  theChart.GetNoCOVRTablePoints(j) * 2 * sizeof(float));
382  pft0nc[j] = pf_entry;
383  pipnc[j] = theChart.GetNoCOVRTablePoints(j);
384  }
385 
386  pNoCovrPlyTable = pfpnc;
387  pNoCovrCntTable = pipnc;
388 }
389 
391 
392 ChartTableEntry::~ChartTableEntry() {
393  free(pFullPath);
394  free(pPlyTable);
395 
396  for (int i = 0; i < nAuxPlyEntries; i++) free(pAuxPlyTable[i]);
397  free(pAuxPlyTable);
398  free(pAuxCntTable);
399 
400  if (nNoCovrPlyEntries) {
401  for (int i = 0; i < nNoCovrPlyEntries; i++) free(pNoCovrPlyTable[i]);
402  free(pNoCovrPlyTable);
403  free(pNoCovrCntTable);
404  }
405 
406  delete m_pfilename;
407  delete m_psFullPath;
408 }
409 
411 
413 
414 bool ChartTableEntry::IsEarlierThan(const ChartTableEntry &cte) const {
415  wxDateTime mine(edition_date);
416  wxDateTime theirs(cte.edition_date);
417 
418  if (!mine.IsValid() || !theirs.IsValid())
419  return false; // will have the effect of keeping all questionable charts
420 
421  return (mine.IsEarlierThan(theirs));
422 }
423 
424 bool ChartTableEntry::IsEqualTo(const ChartTableEntry &cte) const {
425  wxDateTime mine(edition_date);
426  wxDateTime theirs(cte.edition_date);
427 
428  if (!mine.IsValid() || !theirs.IsValid())
429  return true; // will have the effect of keeping all questionable charts
430 
431  return (mine.IsEqualTo(theirs));
432 }
433 
435 
436 static int convertChartType(int charttype) {
437  // Hackeroo here....
438  // dB version 14 had different ChartType Enum, patch it here
439  if (s_dbVersion == 14) {
440  switch (charttype) {
441  case 0:
442  return CHART_TYPE_KAP;
443  case 1:
444  return CHART_TYPE_GEO;
445  case 2:
446  return CHART_TYPE_S57;
447  case 3:
448  return CHART_TYPE_CM93;
449  case 4:
450  return CHART_TYPE_CM93COMP;
451  case 5:
452  return CHART_TYPE_UNKNOWN;
453  case 6:
454  return CHART_TYPE_DONTCARE;
455  case 7:
456  return CHART_TYPE_DUMMY;
457  default:
458  return CHART_TYPE_UNKNOWN;
459  }
460  }
461  return charttype;
462 }
463 
464 static int convertChartFamily(int charttype, int chartfamily) {
465  if (s_dbVersion < 18) {
466  switch (charttype) {
467  case CHART_TYPE_KAP:
468  case CHART_TYPE_GEO:
469  return CHART_FAMILY_RASTER;
470 
471  case CHART_TYPE_S57:
472  case CHART_TYPE_CM93:
473  case CHART_TYPE_CM93COMP:
474  return CHART_FAMILY_VECTOR;
475 
476  default:
477  return CHART_FAMILY_UNKNOWN;
478  }
479  }
480  return chartfamily;
481 }
482 
483 bool ChartTableEntry::Read(const ChartDatabase *pDb, wxInputStream &is) {
484  char path[4096], *cp;
485 
486  Clear();
487 
488  // Allow reading of current db format, and maybe others
489  ChartDatabase *pD = (ChartDatabase *)pDb;
490  int db_version = pD->GetVersion();
491 
492  if (db_version == 18) {
493  // Read the path first
494  for (cp = path; (*cp = (char)is.GetC()) != 0; cp++)
495  ;
496  pFullPath = (char *)malloc(cp - path + 1);
497  strncpy(pFullPath, path, cp - path + 1);
498  wxLogVerbose(_T(" Chart %s"), pFullPath);
499 
500  // Create and populate the helper members
501  m_pfilename = new wxString;
502  wxString fullfilename(pFullPath, wxConvUTF8);
503  wxFileName fn(fullfilename);
504  *m_pfilename = fn.GetFullName();
505  m_psFullPath = new wxString;
506  *m_psFullPath = fullfilename;
507  m_fullSystemPath = fullfilename;
508 
509 #ifdef __OCPN__ANDROID__
510  m_fullSystemPath = wxString(fullfilename.mb_str(wxConvUTF8));
511 #endif
512  // Read the table entry
514  is.Read(&cte, sizeof(ChartTableEntry_onDisk_18));
515 
516  // Transcribe the elements....
517  EntryOffset = cte.EntryOffset;
518  ChartType = cte.ChartType;
519  ChartFamily = cte.ChartFamily;
520  LatMax = cte.LatMax;
521  LatMin = cte.LatMin;
522  LonMax = cte.LonMax;
523  LonMin = cte.LonMin;
524 
525  m_bbox.Set(LatMin, LonMin, LatMax, LonMax);
526 
527  Skew = cte.skew;
528  ProjectionType = cte.ProjectionType;
529 
530  SetScale(cte.Scale);
531  edition_date = cte.edition_date;
532  file_date = cte.file_date;
533 
534  nPlyEntries = cte.nPlyEntries;
535  nAuxPlyEntries = cte.nAuxPlyEntries;
536 
537  nNoCovrPlyEntries = cte.nNoCovrPlyEntries;
538 
539  bValid = cte.bValid;
540 
541  if (nPlyEntries) {
542  int npeSize = nPlyEntries * 2 * sizeof(float);
543  pPlyTable = (float *)malloc(npeSize);
544  is.Read(pPlyTable, npeSize);
545  }
546 
547  if (nAuxPlyEntries) {
548  int napeSize = nAuxPlyEntries * sizeof(int);
549  pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
550  pAuxCntTable = (int *)malloc(napeSize);
551  is.Read(pAuxCntTable, napeSize);
552 
553  for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
554  nAuxPlyEntry++) {
555  int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
556  pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
557  is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
558  }
559  }
560 
561  if (nNoCovrPlyEntries) {
562  int napeSize = nNoCovrPlyEntries * sizeof(int);
563  pNoCovrCntTable = (int *)malloc(napeSize);
564  is.Read(pNoCovrCntTable, napeSize);
565 
566  pNoCovrPlyTable = (float **)malloc(nNoCovrPlyEntries * sizeof(float *));
567  for (int i = 0; i < nNoCovrPlyEntries; i++) {
568  int nfSize = pNoCovrCntTable[i] * 2 * sizeof(float);
569  pNoCovrPlyTable[i] = (float *)malloc(nfSize);
570  is.Read(pNoCovrPlyTable[i], nfSize);
571  }
572  }
573  }
574 
575  else if (db_version == 17) {
576  // Read the path first
577  for (cp = path; (*cp = (char)is.GetC()) != 0; cp++)
578  ;
579  pFullPath = (char *)malloc(cp - path + 1);
580  strncpy(pFullPath, path, cp - path + 1);
581  wxLogVerbose(_T(" Chart %s"), pFullPath);
582 
583  // Create and populate the helper members
584  m_pfilename = new wxString;
585  wxString fullfilename(pFullPath, wxConvUTF8);
586  wxFileName fn(fullfilename);
587  *m_pfilename = fn.GetFullName();
588  m_psFullPath = new wxString;
589  *m_psFullPath = fullfilename;
590 
591  // Read the table entry
593  is.Read(&cte, sizeof(ChartTableEntry_onDisk_17));
594 
595  // Transcribe the elements....
596  EntryOffset = cte.EntryOffset;
597  ChartType = cte.ChartType;
598  LatMax = cte.LatMax;
599  LatMin = cte.LatMin;
600  LonMax = cte.LonMax;
601  LonMin = cte.LonMin;
602 
603  m_bbox.Set(LatMin, LatMax, LonMin, LonMax);
604 
605  Skew = cte.skew;
606  ProjectionType = cte.ProjectionType;
607 
608  SetScale(cte.Scale);
609  edition_date = cte.edition_date;
610  file_date = cte.file_date;
611 
612  nPlyEntries = cte.nPlyEntries;
613  nAuxPlyEntries = cte.nAuxPlyEntries;
614 
615  nNoCovrPlyEntries = cte.nNoCovrPlyEntries;
616 
617  bValid = cte.bValid;
618 
619  if (nPlyEntries) {
620  int npeSize = nPlyEntries * 2 * sizeof(float);
621  pPlyTable = (float *)malloc(npeSize);
622  is.Read(pPlyTable, npeSize);
623  }
624 
625  if (nAuxPlyEntries) {
626  int napeSize = nAuxPlyEntries * sizeof(int);
627  pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
628  pAuxCntTable = (int *)malloc(napeSize);
629  is.Read(pAuxCntTable, napeSize);
630 
631  for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
632  nAuxPlyEntry++) {
633  int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
634  pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
635  is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
636  }
637  }
638 
639  if (nNoCovrPlyEntries) {
640  int napeSize = nNoCovrPlyEntries * sizeof(int);
641  pNoCovrCntTable = (int *)malloc(napeSize);
642  is.Read(pNoCovrCntTable, napeSize);
643 
644  pNoCovrPlyTable = (float **)malloc(nNoCovrPlyEntries * sizeof(float *));
645  for (int i = 0; i < nNoCovrPlyEntries; i++) {
646  int nfSize = pNoCovrCntTable[i] * 2 * sizeof(float);
647  pNoCovrPlyTable[i] = (float *)malloc(nfSize);
648  is.Read(pNoCovrPlyTable[i], nfSize);
649  }
650  }
651  }
652 
653  else if (db_version == 16) {
654  // Read the path first
655  for (cp = path; (*cp = (char)is.GetC()) != 0; cp++)
656  ;
657  // TODO: optimize prepended dir
658  pFullPath = (char *)malloc(cp - path + 1);
659  strncpy(pFullPath, path, cp - path + 1);
660  wxLogVerbose(_T(" Chart %s"), pFullPath);
661 
662  // Create and populate the helper members
663  m_pfilename = new wxString;
664  wxString fullfilename(pFullPath, wxConvUTF8);
665  wxFileName fn(fullfilename);
666  *m_pfilename = fn.GetFullName();
667  m_psFullPath = new wxString;
668  *m_psFullPath = fullfilename;
669 
670  // Read the table entry
672  is.Read(&cte, sizeof(ChartTableEntry_onDisk_16));
673 
674  // Transcribe the elements....
675  EntryOffset = cte.EntryOffset;
676  ChartType = cte.ChartType;
677  LatMax = cte.LatMax;
678  LatMin = cte.LatMin;
679  LonMax = cte.LonMax;
680  LonMin = cte.LonMin;
681 
682  m_bbox.Set(LatMin, LatMax, LonMin, LonMax);
683 
684  Skew = cte.skew;
685  ProjectionType = cte.ProjectionType;
686 
687  SetScale(cte.Scale);
688  edition_date = cte.edition_date;
689  file_date = cte.file_date;
690 
691  nPlyEntries = cte.nPlyEntries;
692  nAuxPlyEntries = cte.nAuxPlyEntries;
693 
694  bValid = cte.bValid;
695 
696  if (nPlyEntries) {
697  int npeSize = nPlyEntries * 2 * sizeof(float);
698  pPlyTable = (float *)malloc(npeSize);
699  is.Read(pPlyTable, npeSize);
700  }
701 
702  if (nAuxPlyEntries) {
703  int napeSize = nAuxPlyEntries * sizeof(int);
704  pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
705  pAuxCntTable = (int *)malloc(napeSize);
706  is.Read(pAuxCntTable, napeSize);
707 
708  for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
709  nAuxPlyEntry++) {
710  int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
711  pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
712  is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
713  }
714  }
715  }
716 
717  else if (db_version == 15) {
718  // Read the path first
719  for (cp = path; (*cp = (char)is.GetC()) != 0; cp++)
720  ;
721  // TODO: optimize prepended dir
722  pFullPath = (char *)malloc(cp - path + 1);
723  strncpy(pFullPath, path, cp - path + 1);
724  wxLogVerbose(_T(" Chart %s"), pFullPath);
725 
726  // Read the table entry
728  is.Read(&cte, sizeof(ChartTableEntry_onDisk_15));
729 
730  // Transcribe the elements....
731  EntryOffset = cte.EntryOffset;
732  ChartType = cte.ChartType;
733  LatMax = cte.LatMax;
734  LatMin = cte.LatMin;
735  LonMax = cte.LonMax;
736  LonMin = cte.LonMin;
737 
738  m_bbox.Set(LatMin, LatMax, LonMin, LonMax);
739 
740  SetScale(cte.Scale);
741  edition_date = cte.edition_date;
742  file_date = cte.file_date;
743 
744  nPlyEntries = cte.nPlyEntries;
745  nAuxPlyEntries = cte.nAuxPlyEntries;
746 
747  bValid = cte.bValid;
748 
749  if (nPlyEntries) {
750  int npeSize = nPlyEntries * 2 * sizeof(float);
751  pPlyTable = (float *)malloc(npeSize);
752  is.Read(pPlyTable, npeSize);
753  }
754 
755  if (nAuxPlyEntries) {
756  int napeSize = nAuxPlyEntries * sizeof(int);
757  pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
758  pAuxCntTable = (int *)malloc(napeSize);
759  is.Read(pAuxCntTable, napeSize);
760 
761  for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
762  nAuxPlyEntry++) {
763  int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
764  pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
765  is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
766  }
767  }
768  } else if (db_version == 14) {
769  // Read the path first
770  for (cp = path; (*cp = (char)is.GetC()) != 0; cp++)
771  ;
772  pFullPath = (char *)malloc(cp - path + 1);
773  strncpy(pFullPath, path, cp - path + 1);
774  wxLogVerbose(_T(" Chart %s"), pFullPath);
775 
776  // Read the table entry
778  is.Read(&cte, sizeof(ChartTableEntry_onDisk_14));
779 
780  // Transcribe the elements....
781  EntryOffset = cte.EntryOffset;
782  ChartType = cte.ChartType;
783  LatMax = cte.LatMax;
784  LatMin = cte.LatMin;
785  LonMax = cte.LonMax;
786  LonMin = cte.LonMin;
787 
788  m_bbox.Set(LatMin, LatMax, LonMin, LonMax);
789 
790  SetScale(cte.Scale);
791  edition_date = cte.edition_date;
792  file_date = 0; // file_date does not exist in V14;
793  nPlyEntries = cte.nPlyEntries;
794  nAuxPlyEntries = cte.nAuxPlyEntries;
795  bValid = cte.bValid;
796 
797  if (nPlyEntries) {
798  int npeSize = nPlyEntries * 2 * sizeof(float);
799  pPlyTable = (float *)malloc(npeSize);
800  is.Read(pPlyTable, npeSize);
801  }
802 
803  if (nAuxPlyEntries) {
804  int napeSize = nAuxPlyEntries * sizeof(int);
805  pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
806  pAuxCntTable = (int *)malloc(napeSize);
807  is.Read(pAuxCntTable, napeSize);
808 
809  for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
810  nAuxPlyEntry++) {
811  int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
812  pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
813  is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
814  }
815  }
816  }
817  ChartFamily = convertChartFamily(ChartType, ChartFamily);
818  ChartType = convertChartType(ChartType);
819 
820  return true;
821 }
822 
824 
825 bool ChartTableEntry::Write(const ChartDatabase *pDb, wxOutputStream &os) {
826  os.Write(pFullPath, strlen(pFullPath) + 1);
827 
828  // Write the current version type only
829  // Create an on_disk table entry
831 
832  // Transcribe the elements....
833  cte.EntryOffset = EntryOffset;
834  cte.ChartType = ChartType;
835  cte.ChartFamily = ChartFamily;
836  cte.LatMax = LatMax;
837  cte.LatMin = LatMin;
838  cte.LonMax = LonMax;
839  cte.LonMin = LonMin;
840 
841  cte.Scale = Scale;
842  cte.edition_date = edition_date;
843  cte.file_date = file_date;
844 
845  cte.nPlyEntries = nPlyEntries;
846  cte.nAuxPlyEntries = nAuxPlyEntries;
847 
848  cte.skew = Skew;
849  cte.ProjectionType = ProjectionType;
850 
851  cte.bValid = bValid;
852 
853  cte.nNoCovrPlyEntries = nNoCovrPlyEntries;
854 
855  os.Write(&cte, sizeof(ChartTableEntry_onDisk_18));
856  wxLogVerbose(_T(" Wrote Chart %s"), pFullPath);
857 
858  // Write out the tables
859  if (nPlyEntries) {
860  int npeSize = nPlyEntries * 2 * sizeof(float);
861  os.Write(pPlyTable, npeSize);
862  }
863 
864  if (nAuxPlyEntries) {
865  int napeSize = nAuxPlyEntries * sizeof(int);
866  os.Write(pAuxCntTable, napeSize);
867 
868  for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries; nAuxPlyEntry++) {
869  int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
870  os.Write(pAuxPlyTable[nAuxPlyEntry], nfSize);
871  }
872  }
873 
874  if (nNoCovrPlyEntries) {
875  int ncSize = nNoCovrPlyEntries * sizeof(int);
876  os.Write(pNoCovrCntTable, ncSize);
877 
878  for (int i = 0; i < nNoCovrPlyEntries; i++) {
879  int nctSize = pNoCovrCntTable[i] * 2 * sizeof(float);
880  os.Write(pNoCovrPlyTable[i], nctSize);
881  }
882  }
883 
884  return true;
885 }
886 
888 
889 void ChartTableEntry::Clear() {
890  // memset(this, 0, sizeof(ChartTableEntry));
891 
892  pFullPath = NULL;
893  pPlyTable = NULL;
894  pAuxPlyTable = NULL;
895  pAuxCntTable = NULL;
896  bValid = false;
897  ;
898  pNoCovrCntTable = NULL;
899  pNoCovrPlyTable = NULL;
900 
901  nNoCovrPlyEntries = 0;
902  nAuxPlyEntries = 0;
903 
904  m_pfilename = NULL; // a helper member, not on disk
905  m_psFullPath = NULL;
906 }
907 
909 
910 void ChartTableEntry::Disable() {
911  // Mark this chart in the database, so that it will not be seen during this
912  // run How? By setting the chart bounding box to an absurd value
913  // TODO... Fix this heinous hack
914  LatMax += (float)1000.;
915  LatMin += (float)1000.;
916 }
917 
918 void ChartTableEntry::ReEnable() {
919  if (LatMax > 90.) {
920  LatMax -= (float)1000.;
921  LatMin -= (float)1000.;
922  }
923 }
924 
925 std::vector<float> ChartTableEntry::GetReducedPlyPoints() {
926  if (m_reducedPlyPoints.size()) return m_reducedPlyPoints;
927 
928  // Reduce the LOD of the chart outline PlyPoints
929  float LOD_meters = 1;
930 
931  float plylat, plylon;
932  const int nPoints = GetnPlyEntries();
933 
934  float *fpo = GetpPlyTable();
935 
936  double *ppd = new double[nPoints * 2];
937  double *ppsm = new double[nPoints * 2];
938  double *npr = ppd;
939  double *npsm = ppsm;
940  for (int i = 0; i < nPoints; i++) {
941  plylat = fpo[i * 2];
942  plylon = fpo[i * 2 + 1];
943 
944  double x, y;
945  toSM(plylat, plylon, fpo[0], fpo[1], &x, &y);
946 
947  *npr++ = plylon;
948  *npr++ = plylat;
949  *npsm++ = x;
950  *npsm++ = y;
951  }
952 
953  std::vector<int> index_keep;
954  if (nPoints > 10) {
955  index_keep.push_back(0);
956  index_keep.push_back(nPoints - 1);
957  index_keep.push_back(1);
958  index_keep.push_back(nPoints - 2);
959 
960  DouglasPeuckerM(ppsm, 1, nPoints - 2, LOD_meters, &index_keep);
961 
962  } else {
963  index_keep.resize(nPoints);
964  for (int i = 0; i < nPoints; i++) index_keep[i] = i;
965  }
966 
967  double *ppr = ppd;
968  for (int ip = 0; ip < nPoints; ip++) {
969  double x = *ppr++;
970  double y = *ppr++;
971 
972  for (unsigned int j = 0; j < index_keep.size(); j++) {
973  if (index_keep[j] == ip) {
974  m_reducedPlyPoints.push_back(x);
975  m_reducedPlyPoints.push_back(y);
976  break;
977  }
978  }
979  }
980 
981  delete[] ppd;
982  delete[] ppsm;
983 
984  int nprr = m_reducedPlyPoints.size() / 2;
985 
986  return m_reducedPlyPoints;
987 }
988 
989 std::vector<float> ChartTableEntry::GetReducedAuxPlyPoints(int iTable) {
990  // Maybe need to initialize the vector
991  if (!m_reducedAuxPlyPointsVector.size()) {
992  std::vector<float> vec;
993  for (int i = 0; i < GetnAuxPlyEntries(); i++) {
994  m_reducedAuxPlyPointsVector.push_back(vec);
995  }
996  }
997 
998  std::vector<float> vec;
999 
1000  // Invalid parameter
1001  if ((unsigned int)iTable >= m_reducedAuxPlyPointsVector.size()) return vec;
1002 
1003  if (m_reducedAuxPlyPointsVector.at(iTable).size())
1004  return m_reducedAuxPlyPointsVector.at(iTable);
1005 
1006  // Reduce the LOD of the chart outline PlyPoints
1007  float LOD_meters = 1.0;
1008 
1009  const int nPoints = GetAuxCntTableEntry(iTable);
1010  float *fpo = GetpAuxPlyTableEntry(iTable);
1011 
1012  double *ppd = new double[nPoints * 2];
1013  double *ppsm = new double[nPoints * 2];
1014  double *npr = ppd;
1015  double *npsm = ppsm;
1016  float plylat, plylon;
1017 
1018  for (int i = 0; i < nPoints; i++) {
1019  plylat = fpo[i * 2];
1020  plylon = fpo[i * 2 + 1];
1021 
1022  double x, y;
1023  toSM(plylat, plylon, fpo[0], fpo[1], &x, &y);
1024 
1025  *npr++ = plylon;
1026  *npr++ = plylat;
1027  *npsm++ = x;
1028  *npsm++ = y;
1029  }
1030 
1031  std::vector<int> index_keep;
1032  if (nPoints > 10) {
1033  index_keep.push_back(0);
1034  index_keep.push_back(nPoints - 1);
1035  index_keep.push_back(1);
1036  index_keep.push_back(nPoints - 2);
1037 
1038  DouglasPeuckerM(ppsm, 1, nPoints - 2, LOD_meters, &index_keep);
1039 
1040  } else {
1041  index_keep.resize(nPoints);
1042  for (int i = 0; i < nPoints; i++) index_keep[i] = i;
1043  }
1044 
1045  int nnn = index_keep.size();
1046 
1047  double *ppr = ppd;
1048  for (int ip = 0; ip < nPoints; ip++) {
1049  double x = *ppr++;
1050  double y = *ppr++;
1051 
1052  for (unsigned int j = 0; j < index_keep.size(); j++) {
1053  if (index_keep[j] == ip) {
1054  vec.push_back(x);
1055  vec.push_back(y);
1056  break;
1057  }
1058  }
1059  }
1060 
1061  delete[] ppd;
1062  delete[] ppsm;
1063 
1064  m_reducedAuxPlyPointsVector[iTable] = vec;
1065 
1066  int nprr = vec.size() / 2;
1067 
1068  return vec;
1069 }
1070 
1072 // ChartDatabase
1074 
1075 WX_DEFINE_OBJARRAY(ChartTable);
1076 
1077 ChartDatabase::ChartDatabase() {
1078  bValid = false;
1079  m_b_busy = false;
1080 
1081  m_ChartTableEntryDummy.Clear();
1082 
1083  UpdateChartClassDescriptorArray();
1084 }
1085 
1086 
1087 void ChartDatabase::UpdateChartClassDescriptorArray(void) {
1088  if(m_ChartClassDescriptorArray.empty()) {
1089  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(_T("ChartKAP"), _T("*.kap"), BUILTIN_DESCRIPTOR));
1090  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(_T("ChartGEO"), _T("*.geo"), BUILTIN_DESCRIPTOR));
1091  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(_T("s57chart"), _T("*.000"), BUILTIN_DESCRIPTOR));
1092  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(_T("s57chart"), _T("*.s57"), BUILTIN_DESCRIPTOR));
1093  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(_T("cm93compchart"), _T("00300000.a"), BUILTIN_DESCRIPTOR));
1094  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(_T("ChartMBTiles"), _T("*.mbtiles"), BUILTIN_DESCRIPTOR));
1095  }
1096  // If the PlugIn Manager exists, get the array of dynamically loadable
1097  // chart class names
1098  if (g_pi_manager) {
1099  m_ChartClassDescriptorArray.erase(std::remove_if(
1100  m_ChartClassDescriptorArray.begin(), m_ChartClassDescriptorArray.end(),
1101  [](const ChartClassDescriptor& cd) {
1102  return cd.m_descriptor_type ==PLUGIN_DESCRIPTOR;
1103  }), m_ChartClassDescriptorArray.end());
1104 
1105  wxArrayString array = g_pi_manager->GetPlugInChartClassNameArray();
1106  for (unsigned int j = 0; j < array.GetCount(); j++) {
1107  // Instantiate a blank chart to retrieve the directory search mask for
1108  // this chart type
1109  wxString class_name = array[j];
1110  ChartPlugInWrapper *cpiw = new ChartPlugInWrapper(class_name);
1111  if (cpiw) {
1112  wxString mask = cpiw->GetFileSearchMask();
1113 
1114  // Create a new descriptor and add it to the database
1115  m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(class_name, mask, PLUGIN_DESCRIPTOR));
1116  delete cpiw;
1117  }
1118  }
1119  }
1120 }
1121 
1122 const ChartTableEntry &ChartDatabase::GetChartTableEntry(int index) const {
1123  if (index < GetChartTableEntries())
1124  return active_chartTable[index];
1125  else
1126  return m_ChartTableEntryDummy;
1127 }
1128 
1129 ChartTableEntry *ChartDatabase::GetpChartTableEntry(int index) const {
1130  if (index < GetChartTableEntries())
1131  return &active_chartTable[index];
1132  else
1133  return (ChartTableEntry *)&m_ChartTableEntryDummy;
1134 }
1135 
1136 bool ChartDatabase::CompareChartDirArray(ArrayOfCDI &test_array) {
1137  // Compare the parameter "test_array" with this.m_dir_array
1138  // Return true if functionally identical (order does not signify).
1139 
1140  if (test_array.GetCount() != m_dir_array.GetCount()) return false;
1141 
1142  bool bfound_inner;
1143  unsigned int nfound_outer = 0;
1144 
1145  for (unsigned int i = 0; i < test_array.GetCount(); i++) {
1146  ChartDirInfo p = test_array[i];
1147  bfound_inner = false;
1148  for (unsigned int j = 0; j < m_dir_array.GetCount(); j++) {
1149  ChartDirInfo q = m_dir_array[j];
1150 
1151  if (p.fullpath.IsSameAs(q.fullpath)) {
1152  bfound_inner = true;
1153  break;
1154  }
1155  }
1156  if (bfound_inner) nfound_outer++;
1157  }
1158 
1159  return (nfound_outer == test_array.GetCount());
1160 }
1161 
1162 wxString ChartDatabase::GetMagicNumberCached(wxString dir) {
1163  for (unsigned int j = 0; j < m_dir_array.GetCount(); j++) {
1164  ChartDirInfo q = m_dir_array[j];
1165  if (dir.IsSameAs(q.fullpath)) return q.magic_number;
1166  }
1167 
1168  return _T("");
1169 }
1170 
1171 bool ChartDatabase::Read(const wxString &filePath) {
1172  ChartTableEntry entry;
1173  int entries;
1174 
1175  bValid = false;
1176 
1177  wxFileName file(filePath);
1178  if (!file.FileExists()) return false;
1179 
1180  m_DBFileName = filePath;
1181 
1182  wxFFileInputStream ifs(filePath);
1183  if (!ifs.Ok()) return false;
1184 
1185  ChartTableHeader cth;
1186  cth.Read(ifs);
1187  if (!cth.CheckValid()) return false;
1188 
1189  // Capture the version number
1190  char vbo[5];
1191  memcpy(vbo, cth.GetDBVersionString(), 4);
1192  vbo[4] = 0;
1193  m_dbversion = atoi(&vbo[1]);
1194  s_dbVersion = m_dbversion; // save the static copy
1195 
1196  wxLogVerbose(wxT("Chartdb:Reading %d directory entries, %d table entries"),
1197  cth.GetDirEntries(), cth.GetTableEntries());
1198  wxLogMessage(_T("Chartdb: Chart directory list follows"));
1199  if (0 == cth.GetDirEntries()) wxLogMessage(_T(" Nil"));
1200 
1201  int ind = 0;
1202  for (int iDir = 0; iDir < cth.GetDirEntries(); iDir++) {
1203  wxString dir;
1204  int dirlen;
1205  ifs.Read(&dirlen, sizeof(int));
1206  while (dirlen > 0) {
1207  char dirbuf[1024];
1208  int alen = dirlen > 1023 ? 1023 : dirlen;
1209  if (ifs.Read(&dirbuf, alen).Eof()) goto read_error;
1210  dirbuf[alen] = 0;
1211  dirlen -= alen;
1212  dir.Append(wxString(dirbuf, wxConvUTF8));
1213  }
1214  wxString msg;
1215  msg.Printf(wxT(" Chart directory #%d: "), iDir);
1216  msg.Append(dir);
1217  wxLogMessage(msg);
1218  m_chartDirs.Add(dir);
1219  }
1220 
1221  entries = cth.GetTableEntries();
1222  active_chartTable.Alloc(entries);
1223  active_chartTable_pathindex.clear();
1224  while (entries-- && entry.Read(this, ifs)) {
1225  active_chartTable_pathindex[entry.GetFullSystemPath()] = ind++;
1226  active_chartTable.Add(entry);
1227  }
1228 
1229  entry.Clear();
1230  bValid = true;
1231  entry.SetAvailable(true);
1232 
1233  m_nentries = active_chartTable.GetCount();
1234  return true;
1235 
1236 read_error:
1237  bValid = false;
1238  m_nentries = active_chartTable.GetCount();
1239  return false;
1240 }
1241 
1243 
1244 bool ChartDatabase::Write(const wxString &filePath) {
1245  wxFileName file(filePath);
1246  wxFileName dir(
1247  file.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME, wxPATH_NATIVE));
1248 
1249  if (!dir.DirExists() && !dir.Mkdir()) return false;
1250 
1251  wxFFileOutputStream ofs(filePath);
1252  if (!ofs.Ok()) return false;
1253 
1254  ChartTableHeader cth(m_chartDirs.GetCount(), active_chartTable.GetCount());
1255  cth.Write(ofs);
1256 
1257  for (int iDir = 0; iDir < cth.GetDirEntries(); iDir++) {
1258  wxString &dir = m_chartDirs[iDir];
1259  int dirlen = dir.length();
1260  char s[200];
1261  strncpy(s, dir.mb_str(wxConvUTF8), 199);
1262  s[199] = 0;
1263  dirlen = strlen(s);
1264  ofs.Write(&dirlen, sizeof(int));
1265  // ofs.Write(dir.fn_str(), dirlen);
1266  ofs.Write(s, dirlen);
1267  }
1268 
1269  for (UINT32 iTable = 0; iTable < active_chartTable.size(); iTable++)
1270  active_chartTable[iTable].Write(this, ofs);
1271 
1272  // Explicitly set the version
1273  m_dbversion = DB_VERSION_CURRENT;
1274 
1275  return true;
1276 }
1277 
1279 wxString SplitPath(wxString s, wxString tkd, int nchar, int offset,
1280  int *pn_split) {
1281  wxString r;
1282  int ncr = 0;
1283 
1284  int rlen = offset;
1285  wxStringTokenizer tkz(s, tkd);
1286  while (tkz.HasMoreTokens()) {
1287  wxString token = tkz.GetNextToken();
1288  if ((rlen + (int)token.Len() + 1) < nchar) {
1289  r += token;
1290  r += tkd[0];
1291  rlen += token.Len() + 1;
1292  } else {
1293  r += _T("\n");
1294  ncr++;
1295  for (int i = 0; i < offset; i++) {
1296  r += _T(" ");
1297  }
1298  r += token;
1299  r += tkd[0];
1300  rlen = offset + token.Len() + 1;
1301  }
1302  }
1303 
1304  if (pn_split) *pn_split = ncr;
1305 
1306  return r.Mid(0, r.Len() - 1); // strip the last separator char
1307 }
1308 
1309 wxString ChartDatabase::GetFullChartInfo(ChartBase *pc, int dbIndex,
1310  int *char_width, int *line_count) {
1311  wxString r;
1312  int lc = 0;
1313  unsigned int max_width = 0;
1314  int ncr;
1315  unsigned int target_width = 60;
1316 
1317  const ChartTableEntry &cte = GetChartTableEntry(dbIndex);
1318  if (1) // TODO why can't this be cte.GetbValid()?
1319  {
1320  wxString line;
1321  line = _(" ChartFile: ");
1322  wxString longline = *(cte.GetpsFullPath());
1323 
1324  if (longline.Len() > target_width) {
1325  line += SplitPath(longline, _T("/,\\"), target_width, 15, &ncr);
1326  max_width = wxMax(max_width, target_width + 4);
1327  lc += ncr;
1328  } else {
1329  line += longline;
1330  max_width = wxMax(max_width, line.Len() + 4);
1331  }
1332 
1333  r += line;
1334  r += _T("\n");
1335  lc++;
1336 
1337  line.Empty();
1338  if (pc) {
1339  line = _(" Name: ");
1340  wxString longline = pc->GetName();
1341 
1342  wxString tkz;
1343  if (longline.Find(' ') != wxNOT_FOUND) // assume a proper name
1344  tkz = _T(" ");
1345  else
1346  tkz = _T("/,\\"); // else a file name
1347 
1348  if (longline.Len() > target_width) {
1349  line += SplitPath(pc->GetName(), tkz, target_width, 12, &ncr);
1350  max_width = wxMax(max_width, target_width + 4);
1351  lc += ncr;
1352  } else {
1353  line += longline;
1354  max_width = wxMax(max_width, line.Len() + 4);
1355  }
1356  }
1357 
1358  line += _T("\n");
1359  r += line;
1360  lc++;
1361 
1362  if (pc) // chart is loaded and available
1363  line.Printf(_T(" %s: 1:%d"), _("Scale"), pc->GetNativeScale());
1364  else
1365  line.Printf(_T(" %s: 1:%d"), _("Scale"), cte.GetScale());
1366 
1367  line += _T("\n");
1368  max_width = wxMax(max_width, line.Len());
1369  r += line;
1370  lc++;
1371 
1372  if (pc) {
1373  line.Empty();
1374  line = _(" ID: ");
1375  line += pc->GetID();
1376  line += _T("\n");
1377  max_width = wxMax(max_width, line.Len());
1378  r += line;
1379  lc++;
1380 
1381  line.Empty();
1382  line = _(" Depth Units: ");
1383  line += pc->GetDepthUnits();
1384  line += _T("\n");
1385  max_width = wxMax(max_width, line.Len());
1386  r += line;
1387  lc++;
1388 
1389  line.Empty();
1390  line = _(" Soundings: ");
1391  line += pc->GetSoundingsDatum();
1392  line += _T("\n");
1393  max_width = wxMax(max_width, line.Len());
1394  r += line;
1395  lc++;
1396 
1397  line.Empty();
1398  line = _(" Datum: ");
1399  line += pc->GetDatumString();
1400  line += _T("\n");
1401  max_width = wxMax(max_width, line.Len());
1402  r += line;
1403  lc++;
1404  }
1405 
1406  line = _(" Projection: ");
1407  if (PROJECTION_UNKNOWN == cte.GetChartProjectionType())
1408  line += _("Unknown");
1409  else if (PROJECTION_MERCATOR == cte.GetChartProjectionType())
1410  line += _("Mercator");
1411  else if (PROJECTION_TRANSVERSE_MERCATOR == cte.GetChartProjectionType())
1412  line += _("Transverse Mercator");
1413  else if (PROJECTION_POLYCONIC == cte.GetChartProjectionType())
1414  line += _("Polyconic");
1415  else if (PROJECTION_WEB_MERCATOR == cte.GetChartProjectionType())
1416  line += _("Web Mercator (EPSG:3857)");
1417  line += _T("\n");
1418  max_width = wxMax(max_width, line.Len());
1419  r += line;
1420  lc++;
1421 
1422  line.Empty();
1423  if (pc) {
1424  line = _(" Source Edition: ");
1425  line += pc->GetSE();
1426  line += _T("\n");
1427  max_width = wxMax(max_width, line.Len());
1428  r += line;
1429  lc++;
1430 
1431  line.Empty();
1432  wxDateTime ed = pc->GetEditionDate();
1433  if (ed.IsValid()) {
1434  line = _(" Updated: ");
1435  line += ed.FormatISODate();
1436  line += _T("\n");
1437  max_width = wxMax(max_width, line.Len());
1438  r += line;
1439  }
1440  lc++;
1441  }
1442 
1443  line.Empty();
1444  if (pc && pc->GetExtraInfo().Len()) {
1445  line += pc->GetExtraInfo();
1446  line += _T("\n");
1447  max_width = wxMax(max_width, line.Len());
1448  r += line;
1449  lc++;
1450  }
1451  }
1452 
1453  if (line_count) *line_count = lc;
1454 
1455  if (char_width) *char_width = max_width;
1456 
1457  return r;
1458 }
1459 
1460 // ----------------------------------------------------------------------------
1461 // Create Chart Table Database by directory search
1462 // resulting in valid pChartTable in (this)
1463 // ----------------------------------------------------------------------------
1464 bool ChartDatabase::Create(ArrayOfCDI &dir_array,
1465  wxGenericProgressDialog *pprog) {
1466  m_dir_array = dir_array;
1467 
1468  bValid = false;
1469 
1470  m_chartDirs.Clear();
1471  active_chartTable.Clear();
1472  active_chartTable_pathindex.clear();
1473 
1474  Update(dir_array, true, pprog); // force the update the reload everything
1475 
1476  bValid = true;
1477 
1478  // Explicitly set the version
1479  m_dbversion = DB_VERSION_CURRENT;
1480 
1481  return true;
1482 }
1483 
1484 // ----------------------------------------------------------------------------
1485 // Update existing ChartTable Database by directory search
1486 // resulting in valid pChartTable in (this)
1487 // ----------------------------------------------------------------------------
1488 bool ChartDatabase::Update(ArrayOfCDI &dir_array, bool bForce,
1489  wxGenericProgressDialog *pprog) {
1490  m_dir_array = dir_array;
1491 
1492  bValid = false; // database is not useable right now...
1493  m_b_busy = true;
1494 
1495  // Mark all charts provisionally invalid
1496  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++)
1497  active_chartTable[i].SetValid(false);
1498 
1499  m_chartDirs.Clear();
1500 
1501  if (bForce) active_chartTable.Clear();
1502 
1503  bool lbForce = bForce;
1504 
1505  // Do a dB Version upgrade if the current one is obsolete
1506  if (s_dbVersion != DB_VERSION_CURRENT) {
1507  active_chartTable.Clear();
1508  lbForce = true;
1509  s_dbVersion = DB_VERSION_CURRENT; // Update the static indicator
1510  m_dbversion = DB_VERSION_CURRENT; // and the member
1511  }
1512 
1513  // Get the new charts
1514 
1515  for (unsigned int j = 0; j < dir_array.GetCount(); j++) {
1516  ChartDirInfo dir_info = dir_array[j];
1517 
1518  // On Android, with SDK >= 30, traversal of a folder that is
1519  // on within the "scoped storage" domain is very slow.
1520  // Aviod it....
1521 #ifdef __OCPN__ANDROID__
1522  if (!androidIsDirWritable(dir_info.fullpath))
1523  continue;
1524 #endif
1525 
1526  wxString dir_magic;
1527 
1528  if (dir_info.fullpath.Find(_T("GSHHG")) != wxNOT_FOUND) {
1529  if (!wxDir::FindFirst(dir_info.fullpath, "poly-*-1.dat").empty()) {
1530  // If some polygons exist in the directory, set it as the one to use for
1531  // GSHHG
1532  // TODO: We should probably compare the version and maybe resolutions
1533  // available with what is currently used...
1534  gWorldMapLocation = dir_info.fullpath + wxFileName::GetPathSeparator();
1535  }
1536  }
1537  if (dir_info.fullpath.Find(_T("OSMSHP")) != wxNOT_FOUND) {
1538  if (!wxDir::FindFirst(dir_info.fullpath, "basemap_*.shp").empty()) {
1539  gWorldShapefileLocation = dir_info.fullpath + wxFileName::GetPathSeparator();
1540  gShapeBasemap.Reset();
1541  }
1542  }
1543 
1544  TraverseDirAndAddCharts(dir_info, pprog, dir_magic, lbForce);
1545 
1546  // Update the dir_list entry, even if the magic values are the same
1547 
1548  dir_info.magic_number = dir_magic;
1549  dir_array.RemoveAt(j);
1550  dir_array.Insert(dir_info, j);
1551 
1552  m_chartDirs.Add(dir_info.fullpath);
1553  } // for
1554 
1555  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
1556  if (!active_chartTable[i].GetbValid()) {
1557  active_chartTable.RemoveAt(i);
1558  i--; // entry is gone, recheck this index for next entry
1559  }
1560  }
1561 
1562  // And once more, setting the Entry index field
1563  active_chartTable_pathindex.clear();
1564  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
1565  active_chartTable_pathindex[active_chartTable[i].GetFullSystemPath()] = i;
1566  active_chartTable[i].SetEntryOffset(i);
1567  }
1568 
1569  m_nentries = active_chartTable.GetCount();
1570 
1571  bValid = true;
1572  m_b_busy = false;
1573  return true;
1574 }
1575 
1576 //-------------------------------------------------------------------
1577 // Find Chart dbIndex
1578 //-------------------------------------------------------------------
1579 
1580 int ChartDatabase::FinddbIndex(wxString PathToFind) {
1581 #if 0
1582  // Find the chart
1583  for(unsigned int i=0 ; i<active_chartTable.GetCount() ; i++)
1584  {
1585  if(active_chartTable[i].GetpsFullPath()->IsSameAs(PathToFind))
1586  {
1587  return i;
1588  }
1589  }
1590 #else
1591  if (active_chartTable_pathindex.find(PathToFind) !=
1592  active_chartTable_pathindex.end())
1593  return active_chartTable_pathindex[PathToFind];
1594 #endif
1595 
1596  return -1;
1597 }
1598 
1599 //-------------------------------------------------------------------
1600 // Disable Chart
1601 //-------------------------------------------------------------------
1602 
1603 int ChartDatabase::DisableChart(wxString &PathToDisable) {
1604  int index = FinddbIndex(PathToDisable);
1605  if (index != -1) {
1606  ChartTableEntry *pentry = &active_chartTable[index];
1607  pentry->Disable();
1608  return 1;
1609  }
1610  return 0;
1611 }
1612 
1613 // ----------------------------------------------------------------------------
1614 // Traverse the given directory looking for charts
1615 // If bupdate is true, also search the existing database for a name match.
1616 // If target chart is already in database, mark the entry valid and skip
1617 // additional processing
1618 // ----------------------------------------------------------------------------
1619 
1620 int ChartDatabase::TraverseDirAndAddCharts(ChartDirInfo &dir_info,
1621  wxGenericProgressDialog *pprog,
1622  wxString &dir_magic, bool bForce) {
1623  // Extract the true dir name and magic number from the compound string
1624  wxString dir_path = dir_info.fullpath;
1625 #ifdef __OCPN__ANDROID__
1626  dir_path = wxString(dir_info.fullpath.mb_str(wxConvUTF8));
1627 #endif
1628 
1629  wxString old_magic = dir_info.magic_number;
1630  wxString new_magic = old_magic;
1631  dir_magic = old_magic; // provisionally the same
1632 
1633  int nAdd = 0;
1634 
1635  bool b_skipDetectDirChange = false;
1636  bool b_dirchange = false;
1637 
1638  // Does this directory actually exist?
1639  if (!wxDir::Exists(dir_path)) return 0;
1640 
1641  // Check to see if this is a cm93 directory root
1642  // If so, skip the DetectDirChange since it may be very slow
1643  // and give no information
1644  // Assume a change has happened, and process accordingly
1645  bool b_cm93 = Check_CM93_Structure(dir_path);
1646  if (b_cm93) {
1647  b_skipDetectDirChange = true;
1648  b_dirchange = true;
1649  }
1650 
1651  // Quick scan the directory to see if it has changed
1652  // If not, there is no need to scan again.....
1653  if (!b_skipDetectDirChange)
1654  b_dirchange = DetectDirChange(dir_path, dir_info.fullpath, old_magic,
1655  new_magic, pprog);
1656 
1657  if (!bForce && !b_dirchange) {
1658  wxString msg(_T(" No change detected on directory "));
1659  msg.Append(dir_path);
1660  wxLogMessage(msg);
1661 
1662  // Traverse the database, and mark as valid all charts coming from this
1663  // dir, or anywhere in its tree
1664 
1665  wxFileName fn_dir(dir_path, _T("stuff"));
1666  unsigned int dir_path_count = fn_dir.GetDirCount();
1667 
1668  if (pprog) pprog->SetTitle(_("OpenCPN Chart Scan...."));
1669 
1670  int nEntries = active_chartTable.GetCount();
1671 
1672  for (int ic = 0; ic < nEntries; ic++) {
1673  wxFileName fn(active_chartTable[ic].GetFullSystemPath());
1674 
1675  while (fn.GetDirCount() >= dir_path_count) {
1676  if (fn.GetPath() == dir_path) {
1677  active_chartTable[ic].SetValid(true);
1678  // if(pprog)
1679  // pprog->Update((ic * 100)
1680  // /nEntries, fn.GetFullPath());
1681 
1682  break;
1683  }
1684  fn.RemoveLastDir();
1685  }
1686  }
1687 
1688  return 0;
1689  }
1690 
1691  // There presumably was a change in the directory contents. Return the new
1692  // magic number
1693  dir_magic = new_magic;
1694 
1695  // Look for all possible defined chart classes
1696  for (auto &cd : m_ChartClassDescriptorArray) {
1697  nAdd += SearchDirAndAddCharts(dir_info.fullpath,
1698  cd, pprog);
1699  }
1700 
1701  return nAdd;
1702 }
1703 
1704 bool ChartDatabase::DetectDirChange(const wxString &dir_path,
1705  const wxString &prog_label,
1706  const wxString &magic, wxString &new_magic,
1707  wxGenericProgressDialog *pprog) {
1708  if (pprog) pprog->SetTitle(_("OpenCPN Directory Scan...."));
1709 
1710  // parse the magic number
1711  long long unsigned int nmagic;
1712  wxULongLong nacc = 0;
1713 
1714  magic.ToULongLong(&nmagic, 10);
1715 
1716  // Get an arraystring of all files
1717  wxArrayString FileList;
1718  wxDir dir(dir_path);
1719  int n_files = dir.GetAllFiles(dir_path, &FileList);
1720  FileList.Sort(); // Ensure persistent order of items being hashed.
1721 
1722  FlexHash hash(sizeof nacc);
1723  hash.Reset();
1724 
1725  // Traverse the list of files, getting their interesting stuff to add to
1726  // accumulator
1727  for (int ifile = 0; ifile < n_files; ifile++) {
1728  if (pprog && (ifile % (n_files / 60 + 1)) == 0)
1729  pprog->Update(wxMin((ifile * 100) / n_files, 100), prog_label);
1730 
1731  wxFileName file(FileList[ifile]);
1732 
1733  // NOTE. Do not ever try to optimize this code by combining `wxString`
1734  // calls. Otherwise `fileNameUTF8` will point to a stale buffer overwritten
1735  // by garbage.
1736  wxString fileNameNative = file.GetFullPath();
1737  wxScopedCharBuffer fileNameUTF8 = fileNameNative.ToUTF8();
1738  hash.Update(fileNameUTF8.data(), fileNameUTF8.length());
1739 
1740  // File Size;
1741  wxULongLong size = file.GetSize();
1742  wxULongLong fileSize = ((size != wxInvalidSize) ? size : 0);
1743  hash.Update(&fileSize, (sizeof fileSize));
1744 
1745  // Mod time, in ticks
1746  wxDateTime t = file.GetModificationTime();
1747  wxULongLong fileTime = t.GetTicks();
1748  hash.Update(&fileTime, (sizeof fileTime));
1749  }
1750 
1751  hash.Finish();
1752  hash.Receive(&nacc);
1753 
1754  // Return the calculated magic number
1755  new_magic = nacc.ToString();
1756 
1757  // And do the test
1758  if (new_magic != magic)
1759  return true;
1760  else
1761  return false;
1762 }
1763 
1764 bool ChartDatabase::IsChartDirUsed(const wxString &theDir) {
1765  wxString dir(theDir);
1766  if (dir.Last() == '/' || dir.Last() == wxFileName::GetPathSeparator())
1767  dir.RemoveLast();
1768 
1769  dir.Append(wxT("*"));
1770  for (UINT32 i = 0; i < active_chartTable.GetCount(); i++) {
1771  if (active_chartTable[i].GetpsFullPath()->Matches(dir)) return true;
1772  }
1773  return false;
1774 }
1775 
1776 //-----------------------------------------------------------------------------
1777 // Validate a given directory as a cm93 root database
1778 // If it appears to be a cm93 database, then return true
1779 //-----------------------------------------------------------------------------
1780 bool ChartDatabase::Check_CM93_Structure(wxString dir_name) {
1781  wxString filespec;
1782 
1783  wxRegEx test(_T("[0-9]+"));
1784 
1785  wxDir dirt(dir_name);
1786  wxString candidate;
1787 
1788  if (dirt.IsOpened())
1789  wxLogMessage(_T("check_cm93 opened dir OK: ") + dir_name);
1790  else {
1791  wxLogMessage(_T("check_cm93 NOT OPENED OK: ") + dir_name);
1792  wxLogMessage(_T("check_cm93 returns false.") + dir_name);
1793  return false;
1794  }
1795 
1796  bool b_maybe_found_cm93 = false;
1797  bool b_cont = dirt.GetFirst(&candidate);
1798 
1799  while (b_cont) {
1800  if (test.Matches(candidate) && (candidate.Len() == 8)) {
1801  b_maybe_found_cm93 = true;
1802  break;
1803  }
1804 
1805  b_cont = dirt.GetNext(&candidate);
1806  }
1807 
1808  if (b_maybe_found_cm93) {
1809  wxString dir_next = dir_name;
1810  dir_next += _T("/");
1811  dir_next += candidate;
1812  if (wxDir::Exists(dir_next)) {
1813  wxDir dir_n(dir_next);
1814  if (dirt.IsOpened()) {
1815  wxString candidate_n;
1816 
1817  wxRegEx test_n(_T("^[A-Ga-g]"));
1818  bool b_probably_found_cm93 = false;
1819  bool b_cont_n = dir_n.IsOpened() && dir_n.GetFirst(&candidate_n);
1820  while (b_cont_n) {
1821  if (test_n.Matches(candidate_n) && (candidate_n.Len() == 1)) {
1822  b_probably_found_cm93 = true;
1823  break;
1824  }
1825  b_cont_n = dir_n.GetNext(&candidate_n);
1826  }
1827 
1828  if (b_probably_found_cm93) // found a directory that looks
1829  // like {dir_name}/12345678/A
1830  // probably cm93
1831  {
1832  // make sure the dir exists
1833  wxString dir_luk = dir_next;
1834  dir_luk += _T("/");
1835  dir_luk += candidate_n;
1836  if (wxDir::Exists(dir_luk)) return true;
1837  }
1838  }
1839  }
1840  }
1841 
1842  return false;
1843 }
1844 
1845 /*
1846 //-----------------------------------------------------------------------------
1847 // Validate a given directory as a cm93 root database
1848 // If it appears to be a cm93 database, then return the name of an existing cell
1849 file
1850 // File name will be unique with respect to member element m_cm93_filename_array
1851 // If not cm93, return empty string
1852 //-----------------------------------------------------------------------------
1853 wxString ChartDatabase::Get_CM93_FileName(wxString dir_name)
1854 {
1855  wxString filespec;
1856 
1857  wxRegEx test(_T("[0-9]+"));
1858 
1859  wxDir dirt(dir_name);
1860  wxString candidate;
1861 
1862  bool b_maybe_found_cm93 = false;
1863  bool b_cont = dirt.GetFirst(&candidate);
1864 
1865  while(b_cont)
1866  {
1867  if(test.Matches(candidate)&& (candidate.Len() == 8))
1868  {
1869  b_maybe_found_cm93 = true;
1870  break;
1871  }
1872 
1873  b_cont = dirt.GetNext(&candidate);
1874 
1875  }
1876 
1877  if(b_maybe_found_cm93)
1878  {
1879  wxString dir_next = dir_name;
1880  dir_next += _T("/");
1881  dir_next += candidate;
1882  if(wxDir::Exists(dir_next))
1883  {
1884  wxDir dir_n(dir_next);
1885  wxString candidate_n;
1886 
1887  wxRegEx test_n(_T("^[A-Ga-g]"));
1888  bool b_probably_found_cm93 = false;
1889  bool b_cont_n = dir_n.GetFirst(&candidate_n);
1890  while(b_cont_n)
1891  {
1892  if(test_n.Matches(candidate_n) && (candidate_n.Len() ==
1893 1))
1894  {
1895  b_probably_found_cm93 = true;
1896  break;
1897  }
1898  b_cont_n = dir_n.GetNext(&candidate_n);
1899  }
1900 
1901  if(b_probably_found_cm93) // found a directory that
1902 looks like {dir_name}/12345678/A probably cm93 { // and we want to try and
1903 shorten the recursive search
1904  // make sure the dir exists
1905  wxString dir_luk = dir_next;
1906  dir_luk += _T("/");
1907  dir_luk += candidate_n;
1908  if(wxDir::Exists(dir_luk))
1909  {
1910  wxString msg(_T("Found probable CM93 database in
1911 ")); msg += dir_name; wxLogMessage(msg);
1912 
1913  wxString dir_name_plus = dir_luk; // be very
1914 specific about the dir_name,
1915 
1916  wxDir dir_get(dir_name_plus);
1917  wxString one_file;
1918  dir_get.GetFirst(&one_file);
1919 
1920  // We must return a unique file name, i.e. one
1921 that has not bee seen
1922  // before in this invocation of chart dir
1923 scans. bool find_unique = false; while(!find_unique)
1924  {
1925  find_unique = true;
1926  for(unsigned int ifile=0; ifile <
1927 m_cm93_filename_array.GetCount(); ifile++)
1928  {
1929  if(m_cm93_filename_array[ifile] ==
1930 one_file) find_unique = false;
1931  }
1932  if(!find_unique)
1933  dir_get.GetNext(&one_file);
1934  }
1935 
1936  m_cm93_filename_array.Add(one_file);
1937 
1938  filespec = one_file;
1939  }
1940 
1941  }
1942  }
1943  }
1944 
1945  return filespec;
1946 }
1947 */
1948 
1949 // ----------------------------------------------------------------------------
1950 // Populate Chart Table by directory search for specified file type
1951 // If bupdate flag is true, search the Chart Table for matching chart.
1952 // if target chart is already in table, mark it valid and skip chart processing
1953 // ----------------------------------------------------------------------------
1954 
1955 WX_DECLARE_STRING_HASH_MAP(int, ChartCollisionsHashMap);
1956 
1957 int ChartDatabase::SearchDirAndAddCharts(wxString &dir_name_base,
1958  ChartClassDescriptor &chart_desc,
1959  wxGenericProgressDialog *pprog) {
1960  wxString msg(_T("Searching directory: "));
1961  msg += dir_name_base;
1962  msg += _T(" for ");
1963  msg += chart_desc.m_search_mask;
1964  wxLogMessage(msg);
1965 
1966  wxString dir_name = dir_name_base;
1967 
1968 #ifdef __OCPN__ANDROID__
1969  dir_name = wxString(dir_name_base.mb_str(wxConvUTF8)); // android
1970 #endif
1971 
1972  if (!wxDir::Exists(dir_name)) return 0;
1973 
1974  wxString filespec = chart_desc.m_search_mask.Upper();
1975  wxString lowerFileSpec = chart_desc.m_search_mask.Lower();
1976  wxString filespecXZ = filespec + _T(".xz");
1977  wxString lowerFileSpecXZ = lowerFileSpec + _T(".xz");
1978  wxString filename;
1979 
1980  // Count the files
1981  wxArrayString FileList;
1982  int gaf_flags = wxDIR_DEFAULT; // as default, recurse into subdirs
1983 
1984  // Here is an optimization for MSW/cm93 especially
1985  // If this directory seems to be a cm93, and we are not explicitely looking
1986  // for cm93, then abort Otherwise, we will be looking thru entire cm93 tree
1987  // for non-existent .KAP files, etc.
1988 
1989  bool b_found_cm93 = false;
1990  bool b_cm93 = Check_CM93_Structure(dir_name);
1991  if (b_cm93) {
1992  if (filespec != _T("00300000.A"))
1993  return false;
1994  else {
1995  filespec = dir_name;
1996  b_found_cm93 = true;
1997  }
1998  }
1999 
2000  if (!b_found_cm93) {
2001 
2002  wxDir dir(dir_name);
2003  dir.GetAllFiles(dir_name, &FileList, filespec, gaf_flags);
2004 
2005 #ifdef __OCPN__ANDROID__
2006  if (!FileList.GetCount()) {
2007  wxArrayString afl = androidTraverseDir(dir_name, filespec);
2008  for (wxArrayString::const_iterator item = afl.begin(); item != afl.end();
2009  item++)
2010  FileList.Add(*item);
2011  }
2012 #endif
2013 
2014 
2015 #ifndef __WXMSW__
2016  if (filespec != lowerFileSpec) {
2017  // add lowercase filespec files too
2018  wxArrayString lowerFileList;
2019  dir.GetAllFiles(dir_name, &lowerFileList, lowerFileSpec, gaf_flags);
2020 
2021 
2022 #ifdef __OCPN__ANDROID__
2023  if (!lowerFileList.GetCount()) {
2024  wxArrayString afl = androidTraverseDir(dir_name, lowerFileSpec);
2025  for (wxArrayString::const_iterator item = afl.begin();
2026  item != afl.end(); item++)
2027  lowerFileList.Add(*item);
2028  }
2029 #endif
2030 
2031  for (wxArrayString::const_iterator item = lowerFileList.begin();
2032  item != lowerFileList.end(); item++)
2033  FileList.Add(*item);
2034  }
2035 #endif
2036 
2037 #ifdef OCPN_USE_LZMA
2038  // add xz compressed files;
2039  dir.GetAllFiles(dir_name, &FileList, filespecXZ, gaf_flags);
2040  dir.GetAllFiles(dir_name, &FileList, lowerFileSpecXZ, gaf_flags);
2041 #endif
2042 
2043 
2044  FileList.Sort(); // Sorted processing order makes the progress bar more
2045  // meaningful to the user.
2046  } else { // This is a cm93 dataset, specified as yada/yada/cm93
2047  wxString dir_plus = dir_name;
2048  dir_plus += wxFileName::GetPathSeparator();
2049  FileList.Add(dir_plus);
2050  }
2051 
2052  int nFile = FileList.GetCount();
2053 
2054  if (!nFile) return false;
2055 
2056  int nDirEntry = 0;
2057 
2058  // Check to see if there are any charts in the DB which refer to this
2059  // directory If none at all, there is no need to scan the DB for fullpath
2060  // match of each potential addition and bthis_dir_in_dB is false.
2061  bool bthis_dir_in_dB = IsChartDirUsed(dir_name);
2062 
2063  if (pprog) pprog->SetTitle(_("OpenCPN Chart Add...."));
2064 
2065  // build a hash table based on filename (without directory prefix) of
2066  // the chart to fast to detect identical charts
2067  ChartCollisionsHashMap collision_map;
2068  int nEntry = active_chartTable.GetCount();
2069  for (int i = 0; i < nEntry; i++) {
2070  wxString table_file_name = active_chartTable[i].GetFullSystemPath();
2071  wxFileName table_file(table_file_name);
2072  collision_map[table_file.GetFullName()] = i;
2073  }
2074 
2075  int nFileProgressQuantum = wxMax(nFile / 100, 2);
2076  double rFileProgressRatio = 100.0 / wxMax(nFile, 1);
2077 
2078  for (int ifile = 0; ifile < nFile; ifile++) {
2079  wxFileName file(FileList[ifile]);
2080  wxString full_name = file.GetFullPath();
2081  wxString file_name = file.GetFullName();
2082  wxString utf8_path = full_name;
2083 
2084 #ifdef __OCPN__ANDROID__
2085  // The full path (full_name) is the broken Android files system
2086  // interpretation, which does not display well onscreen. So, here we
2087  // reconstruct a full path spec in UTF-8 encoding for later use in string
2088  // displays. This utf-8 string will be used to construct the chart database
2089  // entry if required.
2090  wxFileName fnbase(dir_name_base);
2091  int nDirs = fnbase.GetDirCount();
2092 
2093  wxFileName file_target(FileList[ifile]);
2094 
2095  for (int i = 0; i < nDirs + 1;
2096  i++) // strip off the erroneous intial directories
2097  file_target.RemoveDir(0);
2098 
2099  wxString leftover_path = file_target.GetFullPath();
2100  utf8_path =
2101  dir_name_base + leftover_path; // reconstruct a fully utf-8 version
2102 #endif
2103 
2104  // Validate the file name again, considering MSW's semi-random treatment
2105  // of case....
2106  // TODO...something fishy here - may need to normalize saved name?
2107  if (!file_name.Matches(lowerFileSpec) && !file_name.Matches(filespec) &&
2108  !file_name.Matches(lowerFileSpecXZ) && !file_name.Matches(filespecXZ) &&
2109  !b_found_cm93) {
2110  // wxLogMessage(_T("FileSpec test failed for:") + file_name);
2111  continue;
2112  }
2113 
2114  if (pprog && ((ifile % nFileProgressQuantum) == 0))
2115  pprog->Update(static_cast<int>(ifile * rFileProgressRatio), utf8_path);
2116 
2117  ChartTableEntry *pnewChart = NULL;
2118  bool bAddFinal = true;
2119  int b_add_msg = 0;
2120 
2121  // Check the collisions map looking for duplicates, and choosing the right
2122  // one.
2123  ChartCollisionsHashMap::const_iterator collision_ptr =
2124  collision_map.find(file_name);
2125  bool collision = (collision_ptr != collision_map.end());
2126  bool file_path_is_same = false;
2127  bool file_time_is_same = false;
2128  ChartTableEntry *pEntry = NULL;
2129  wxString table_file_name;
2130 
2131  if (collision) {
2132  pEntry = &active_chartTable[collision_ptr->second];
2133  table_file_name = pEntry->GetFullSystemPath();
2134  file_path_is_same =
2135  bthis_dir_in_dB && full_name.IsSameAs(table_file_name);
2136 
2137  // If the chart full file paths are exactly the same, select the newer
2138  // one.
2139  if (file_path_is_same) {
2140  b_add_msg++;
2141 
2142  // Check the file modification time
2143  time_t t_oldFile = pEntry->GetFileTime();
2144  time_t t_newFile = file.GetModificationTime().GetTicks();
2145 
2146  if (t_newFile <= t_oldFile) {
2147  file_time_is_same = true;
2148  bAddFinal = false;
2149  pEntry->SetValid(true);
2150  } else {
2151  bAddFinal = true;
2152  pEntry->SetValid(false);
2153  }
2154  }
2155  }
2156 
2157  wxString msg_fn(full_name);
2158  msg_fn.Replace(_T("%"), _T("%%"));
2159  if (file_time_is_same) {
2160  // Produce the same output without actually calling
2161  // `CreateChartTableEntry()`.
2162  wxLogMessage(
2163  wxString::Format(_T("Loading chart data for %s"), msg_fn.c_str()));
2164  } else {
2165  pnewChart = CreateChartTableEntry(full_name, utf8_path, chart_desc);
2166  if (!pnewChart) {
2167  bAddFinal = false;
2168  wxLogMessage(wxString::Format(
2169  _T(" CreateChartTableEntry() failed for file: %s"),
2170  msg_fn.c_str()));
2171  }
2172  }
2173 
2174  if (!collision || !pnewChart) {
2175  // Do nothing.
2176  } else if (file_path_is_same) {
2177  wxLogMessage(
2178  wxString::Format(_T(" Replacing older chart file of same path: %s"),
2179  msg_fn.c_str()));
2180  } else if (!file_time_is_same) {
2181  // Look at the chart file name (without directory prefix) for a further
2182  // check for duplicates This catches the case in which the "same" chart
2183  // is in different locations, and one may be newer than the other.
2184  b_add_msg++;
2185 
2186  if (pnewChart->IsEarlierThan(*pEntry)) {
2187  wxFileName table_file(table_file_name);
2188  // Make sure the compare file actually exists
2189  if (table_file.IsFileReadable()) {
2190  pEntry->SetValid(true);
2191  bAddFinal = false;
2192  wxLogMessage(wxString::Format(
2193  _T(" Retaining newer chart file of same name: %s"),
2194  msg_fn.c_str()));
2195  }
2196  } else if (pnewChart->IsEqualTo(*pEntry)) {
2197  // The file names (without dir prefix) are identical,
2198  // and the mod times are identical
2199  // Prsume that this is intentional, in order to facilitate
2200  // having the same chart in multiple groups.
2201  // So, add this chart.
2202  bAddFinal = true;
2203  } else {
2204  pEntry->SetValid(false);
2205  bAddFinal = true;
2206  wxLogMessage(wxString::Format(
2207  _T(" Replacing older chart file of same name: %s"),
2208  msg_fn.c_str()));
2209  }
2210  }
2211 
2212  if (bAddFinal) {
2213  if (0 == b_add_msg) {
2214  wxLogMessage(
2215  wxString::Format(_T(" Adding chart file: %s"), msg_fn.c_str()));
2216  }
2217  collision_map[file_name] = active_chartTable.GetCount();
2218  active_chartTable.Add(pnewChart);
2219  nDirEntry++;
2220  } else {
2221  if (pnewChart) delete pnewChart;
2222  // wxLogMessage(wxString::Format(_T(" Not adding
2223  // chart file: %s"), msg_fn.c_str()));
2224  }
2225  }
2226 
2227  m_nentries = active_chartTable.GetCount();
2228 
2229  return nDirEntry;
2230 }
2231 
2232 bool ChartDatabase::AddChart(wxString &chartfilename,
2233  ChartClassDescriptor &chart_desc,
2234  wxGenericProgressDialog *pprog, int isearch,
2235  bool bthis_dir_in_dB) {
2236  bool rv = false;
2237  wxFileName file(chartfilename);
2238  wxString full_name = file.GetFullPath();
2239  wxString file_name = file.GetFullName();
2240 
2241  // Validate the file name again, considering MSW's semi-random treatment of
2242  // case....
2243  // TODO...something fishy here - may need to normalize saved name?
2244  // if(!file_name.Matches(lowerFileSpec) && !file_name.Matches(filespec) &&
2245  // !b_found_cm93)
2246  // continue;
2247 
2248  if (pprog)
2249  pprog->Update(wxMin((m_pdifile * 100) / m_pdnFile, 100), full_name);
2250 
2251  ChartTableEntry *pnewChart = NULL;
2252  bool bAddFinal = true;
2253  int b_add_msg = 0;
2254  wxString msg_fn(full_name);
2255  msg_fn.Replace(_T("%"), _T("%%"));
2256 
2257  pnewChart = CreateChartTableEntry(full_name, full_name, chart_desc);
2258  if (!pnewChart) {
2259  bAddFinal = false;
2260  wxLogMessage(wxString::Format(
2261  _T(" CreateChartTableEntry() failed for file: %s"), msg_fn.c_str()));
2262  return false;
2263  } else // traverse the existing database looking for duplicates, and choosing
2264  // the right one
2265  {
2266  int nEntry = active_chartTable.GetCount();
2267  for (int i = 0; i < nEntry; i++) {
2268  wxString *ptable_file_name = active_chartTable[isearch].GetpsFullPath();
2269 
2270  // If the chart full file paths are exactly the same, select the newer
2271  // one
2272  if (bthis_dir_in_dB && full_name.IsSameAs(*ptable_file_name)) {
2273  b_add_msg++;
2274 
2275  // Check the file modification time
2276  time_t t_oldFile = active_chartTable[isearch].GetFileTime();
2277  time_t t_newFile = file.GetModificationTime().GetTicks();
2278 
2279  if (t_newFile <= t_oldFile) {
2280  bAddFinal = false;
2281  active_chartTable[isearch].SetValid(true);
2282  } else {
2283  bAddFinal = true;
2284  active_chartTable[isearch].SetValid(false);
2285  wxLogMessage(wxString::Format(
2286  _T(" Replacing older chart file of same path: %s"),
2287  msg_fn.c_str()));
2288  }
2289 
2290  break;
2291  }
2292 
2293  // Look at the chart file name (without directory prefix) for a further
2294  // check for duplicates This catches the case in which the "same" chart
2295  // is in different locations, and one may be newer than the other.
2296  wxFileName table_file(*ptable_file_name);
2297 
2298  if (table_file.GetFullName() == file_name) {
2299  b_add_msg++;
2300 
2301  if (pnewChart->IsEarlierThan(active_chartTable[isearch])) {
2302  // Make sure the compare file actually exists
2303  if (table_file.IsFileReadable()) {
2304  active_chartTable[isearch].SetValid(true);
2305  bAddFinal = false;
2306  wxLogMessage(wxString::Format(
2307  _T(" Retaining newer chart file of same name: %s"),
2308  msg_fn.c_str()));
2309  }
2310  } else if (pnewChart->IsEqualTo(active_chartTable[isearch])) {
2311  // The file names (without dir prefix) are identical,
2312  // and the mod times are identical
2313  // Prsume that this is intentional, in order to facilitate
2314  // having the same chart in multiple groups.
2315  // So, add this chart.
2316  bAddFinal = true;
2317  }
2318 
2319  else {
2320  active_chartTable[isearch].SetValid(false);
2321  bAddFinal = true;
2322  wxLogMessage(wxString::Format(
2323  _T(" Replacing older chart file of same name: %s"),
2324  msg_fn.c_str()));
2325  }
2326 
2327  break;
2328  }
2329 
2330  // TODO Look at the chart ID as a further check against duplicates
2331 
2332  isearch++;
2333  if (nEntry == isearch) isearch = 0;
2334  } // for
2335  }
2336 
2337  if (bAddFinal) {
2338  if (0 == b_add_msg) {
2339  wxLogMessage(
2340  wxString::Format(_T(" Adding chart file: %s"), msg_fn.c_str()));
2341  }
2342 
2343  active_chartTable.Add(pnewChart);
2344 
2345  rv = true;
2346  } else {
2347  delete pnewChart;
2348  // wxLogMessage(wxString::Format(_T(" Not adding chart
2349  // file: %s"), msg_fn.c_str()));
2350  rv = false;
2351  }
2352 
2353  m_nentries = active_chartTable.GetCount();
2354 
2355  return rv;
2356 }
2357 
2358 bool ChartDatabase::AddSingleChart(wxString &ChartFullPath,
2359  bool b_force_full_search) {
2360  // Find a relevant chart class descriptor
2361  wxFileName fn(ChartFullPath);
2362  wxString ext = fn.GetExt();
2363  ext.Prepend(_T("*."));
2364  wxString ext_upper = ext.MakeUpper();
2365  wxString ext_lower = ext.MakeLower();
2366  wxString dir_name = fn.GetPath();
2367 
2368  // Search the array of chart class descriptors to find a match
2369  // bewteen the search mask and the the chart file extension
2370 
2371  ChartClassDescriptor desc;
2372  for (auto &cd : m_ChartClassDescriptorArray) {
2373  if (cd.m_descriptor_type == PLUGIN_DESCRIPTOR) {
2374  if (cd.m_search_mask == ext_upper) {
2375  desc = cd;
2376  break;
2377  }
2378  if (cd.m_search_mask == ext_lower) {
2379  desc = cd;
2380  break;
2381  }
2382  }
2383  }
2384 
2385  // If we know that we need to do a full recursive search of the db,
2386  // then there is no need to verify it by doing a directory match
2387  bool b_recurse = true;
2388  if (!b_force_full_search) b_recurse = IsChartDirUsed(dir_name);
2389 
2390  bool rv = AddChart(ChartFullPath, desc, NULL, 0, b_recurse);
2391 
2392  // remove duplicates marked in AddChart()
2393 
2394  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
2395  if (!active_chartTable[i].GetbValid()) {
2396  active_chartTable.RemoveAt(i);
2397  i--; // entry is gone, recheck this index for next entry
2398  }
2399  }
2400 
2401  // Update the Entry index fields
2402  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++)
2403  active_chartTable[i].SetEntryOffset(i);
2404 
2405  // Get a new magic number
2406  wxString new_magic;
2407  DetectDirChange(dir_name, _T(""), _T(""), new_magic, 0);
2408 
2409  // Update (clone) the CDI array
2410  bool bcfound = false;
2411  ArrayOfCDI NewChartDirArray;
2412 
2413  ArrayOfCDI ChartDirArray = GetChartDirArray();
2414  for (unsigned int i = 0; i < ChartDirArray.GetCount(); i++) {
2415  ChartDirInfo cdi = ChartDirArray[i];
2416 
2417  ChartDirInfo newcdi = cdi;
2418 
2419  // If entry is found that matches this cell, clear the magic number.
2420  if (newcdi.fullpath == dir_name) {
2421  newcdi.magic_number = new_magic;
2422  bcfound = true;
2423  }
2424 
2425  NewChartDirArray.Add(newcdi);
2426  }
2427 
2428  if (!bcfound) {
2429  ChartDirInfo cdi;
2430  cdi.fullpath = dir_name;
2431  cdi.magic_number = new_magic;
2432  NewChartDirArray.Add(cdi);
2433  }
2434 
2435  // Update the database master copy of the CDI array
2436  SetChartDirArray(NewChartDirArray);
2437 
2438  // Update the list of chart dirs.
2439  m_chartDirs.Clear();
2440 
2441  for (unsigned int i = 0; i < GetChartDirArray().GetCount(); i++) {
2442  ChartDirInfo cdi = GetChartDirArray()[i];
2443  m_chartDirs.Add(cdi.fullpath);
2444  }
2445 
2446  m_nentries = active_chartTable.GetCount();
2447 
2448  return rv;
2449 }
2450 
2451 bool ChartDatabase::RemoveSingleChart(wxString &ChartFullPath) {
2452  bool rv = false;
2453 
2454  // Walk the chart table, looking for the target
2455  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
2456  if (ChartFullPath.IsSameAs(GetChartTableEntry(i).GetFullSystemPath())) {
2457  active_chartTable.RemoveAt(i);
2458  break;
2459  }
2460  }
2461 
2462  // Update the EntryOffset fields for the array
2463  ChartTableEntry *pcte;
2464 
2465  for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
2466  pcte = GetpChartTableEntry(i);
2467  pcte->SetEntryOffset(i);
2468  }
2469 
2470  // Check and update the dir array
2471  wxFileName fn(ChartFullPath);
2472  wxString fd = fn.GetPath();
2473  if (!IsChartDirUsed(fd)) {
2474  // Clone a new array, removing the unused directory,
2475  ArrayOfCDI NewChartDirArray;
2476 
2477  ArrayOfCDI ChartDirArray = GetChartDirArray();
2478  for (unsigned int i = 0; i < ChartDirArray.GetCount(); i++) {
2479  ChartDirInfo cdi = ChartDirArray[i];
2480 
2481  ChartDirInfo newcdi = cdi;
2482 
2483  if (newcdi.fullpath != fd) NewChartDirArray.Add(newcdi);
2484  }
2485 
2486  SetChartDirArray(NewChartDirArray);
2487  }
2488 
2489  // Update the list of chart dirs.
2490  m_chartDirs.Clear();
2491  for (unsigned int i = 0; i < GetChartDirArray().GetCount(); i++) {
2492  ChartDirInfo cdi = GetChartDirArray()[i];
2493  m_chartDirs.Add(cdi.fullpath);
2494  }
2495 
2496  m_nentries = active_chartTable.GetCount();
2497 
2498  return rv;
2499 }
2500 
2502 // Create a Chart object
2504 
2505 ChartBase *ChartDatabase::GetChart(const wxChar *theFilePath,
2506  ChartClassDescriptor &chart_desc) const {
2507  // TODO: support non-UI chart factory
2508  return NULL;
2509 }
2510 
2512 // Create Chart Table entry by reading chart header info, etc.
2514 
2515 ChartTableEntry *ChartDatabase::CreateChartTableEntry(
2516  const wxString &filePath, wxString &utf8Path,
2517  ChartClassDescriptor &chart_desc) {
2518  wxString msg_fn(filePath);
2519  msg_fn.Replace(_T("%"), _T("%%"));
2520  wxLogMessage(
2521  wxString::Format(_T("Loading chart data for %s"), msg_fn.c_str()));
2522 
2523  ChartBase *pch = GetChart(filePath, chart_desc);
2524  if (pch == NULL) {
2525  wxLogMessage(
2526  wxString::Format(_T(" ...creation failed for %s"), msg_fn.c_str()));
2527  return NULL;
2528  }
2529 
2530  InitReturn rc = pch->Init(filePath, HEADER_ONLY);
2531  if (rc != INIT_OK) {
2532  delete pch;
2533  wxLogMessage(wxString::Format(_T(" ...initialization failed for %s"),
2534  msg_fn.c_str()));
2535  return NULL;
2536  }
2537 
2538  ChartTableEntry *ret_val = new ChartTableEntry(*pch, utf8Path);
2539  ret_val->SetValid(true);
2540 
2541  delete pch;
2542 
2543  return ret_val;
2544 }
2545 
2546 bool ChartDatabase::GetCentroidOfLargestScaleChart(double *clat, double *clon,
2547  ChartFamilyEnum family) {
2548  int cur_max_i = -1;
2549  int cur_max_scale = 0;
2550 
2551  int nEntry = active_chartTable.GetCount();
2552 
2553  for (int i = 0; i < nEntry; i++) {
2554  if (GetChartFamily(active_chartTable[i].GetChartType()) == family) {
2555  if (active_chartTable[i].GetScale() > cur_max_scale) {
2556  cur_max_scale = active_chartTable[i].GetScale();
2557  cur_max_i = i;
2558  }
2559  }
2560  }
2561 
2562  if (cur_max_i == -1)
2563  return false; // nothing found
2564  else {
2565  *clat = (active_chartTable[cur_max_i].GetLatMax() +
2566  active_chartTable[cur_max_i].GetLatMin()) /
2567  2.;
2568  *clon = (active_chartTable[cur_max_i].GetLonMin() +
2569  active_chartTable[cur_max_i].GetLonMax()) /
2570  2.;
2571  }
2572  return true;
2573 }
2574 
2575 //-------------------------------------------------------------------
2576 // Get DBChart Projection
2577 //-------------------------------------------------------------------
2578 int ChartDatabase::GetDBChartProj(int dbIndex) {
2579  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size()))
2580  return active_chartTable[dbIndex].GetChartProjectionType();
2581  else
2582  return PROJECTION_UNKNOWN;
2583 }
2584 
2585 //-------------------------------------------------------------------
2586 // Get DBChart Family
2587 //-------------------------------------------------------------------
2588 int ChartDatabase::GetDBChartFamily(int dbIndex) {
2589  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size()))
2590  return active_chartTable[dbIndex].GetChartFamily();
2591  else
2592  return CHART_FAMILY_UNKNOWN;
2593 }
2594 
2595 //-------------------------------------------------------------------
2596 // Get DBChart FullFileName
2597 //-------------------------------------------------------------------
2598 wxString ChartDatabase::GetDBChartFileName(int dbIndex) {
2599  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2600  return wxString(active_chartTable[dbIndex].GetFullSystemPath());
2601  } else
2602  return _T("");
2603 }
2604 
2605 //-------------------------------------------------------------------
2606 // Get DBChart Type
2607 //-------------------------------------------------------------------
2608 int ChartDatabase::GetDBChartType(int dbIndex) {
2609  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size()))
2610  return active_chartTable[dbIndex].GetChartType();
2611  else
2612  return 0;
2613 }
2614 
2615 //-------------------------------------------------------------------
2616 // Get DBChart Skew
2617 //-------------------------------------------------------------------
2618 float ChartDatabase::GetDBChartSkew(int dbIndex) {
2619  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size()))
2620  return active_chartTable[dbIndex].GetChartSkew();
2621  else
2622  return 0.;
2623 }
2624 
2625 //-------------------------------------------------------------------
2626 // Get DBChart Scale
2627 //-------------------------------------------------------------------
2628 int ChartDatabase::GetDBChartScale(int dbIndex) {
2629  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size()))
2630  return active_chartTable[dbIndex].GetScale();
2631  else
2632  return 1;
2633 }
2634 
2635 //-------------------------------------------------------------------
2636 // Get Lat/Lon Bounding Box from db
2637 //-------------------------------------------------------------------
2638 bool ChartDatabase::GetDBBoundingBox(int dbIndex, LLBBox &box) {
2639  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2640  const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
2641  box.Set(entry.GetLatMin(), entry.GetLonMin(), entry.GetLatMax(),
2642  entry.GetLonMax());
2643  }
2644 
2645  return true;
2646 }
2647 
2648 const LLBBox &ChartDatabase::GetDBBoundingBox(int dbIndex) {
2649  if ((bValid) && (dbIndex >= 0)) {
2650  const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
2651  return entry.GetBBox();
2652  } else {
2653  return m_dummy_bbox;
2654  }
2655 }
2656 
2657 //-------------------------------------------------------------------
2658 // Get PlyPoint from Database
2659 //-------------------------------------------------------------------
2660 int ChartDatabase::GetDBPlyPoint(int dbIndex, int plyindex, float *lat,
2661  float *lon) {
2662  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2663  const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
2664  if (entry.GetnPlyEntries()) {
2665  float *fp = entry.GetpPlyTable();
2666  fp += plyindex * 2;
2667  if (lat) *lat = *fp;
2668  fp++;
2669  if (lon) *lon = *fp;
2670  }
2671  return entry.GetnPlyEntries();
2672  } else
2673  return 0;
2674 }
2675 
2676 //-------------------------------------------------------------------
2677 // Get AuxPlyPoint from Database
2678 //-------------------------------------------------------------------
2679 int ChartDatabase::GetDBAuxPlyPoint(int dbIndex, int plyindex, int ply,
2680  float *lat, float *lon) {
2681  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2682  const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
2683  if (entry.GetnAuxPlyEntries()) {
2684  float *fp = entry.GetpAuxPlyTableEntry(ply);
2685 
2686  fp += plyindex * 2;
2687  if (lat) *lat = *fp;
2688  fp++;
2689  if (lon) *lon = *fp;
2690  }
2691 
2692  return entry.GetAuxCntTableEntry(ply);
2693  } else
2694  return 0;
2695 }
2696 
2697 int ChartDatabase::GetnAuxPlyEntries(int dbIndex) {
2698  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2699  const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
2700  return entry.GetnAuxPlyEntries();
2701  } else
2702  return 0;
2703 }
2704 
2705 //-------------------------------------------------------------------
2706 // Get vector of reduced Plypoints
2707 //-------------------------------------------------------------------
2708 std::vector<float> ChartDatabase::GetReducedPlyPoints(int dbIndex) {
2709  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2710  ChartTableEntry *pentry = GetpChartTableEntry(dbIndex);
2711  if (pentry) return pentry->GetReducedPlyPoints();
2712  }
2713 
2714  std::vector<float> dummy;
2715  return dummy;
2716 }
2717 
2718 //-------------------------------------------------------------------
2719 // Get vector of reduced AuxPlypoints
2720 //-------------------------------------------------------------------
2721 std::vector<float> ChartDatabase::GetReducedAuxPlyPoints(int dbIndex,
2722  int iTable) {
2723  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2724  ChartTableEntry *pentry = GetpChartTableEntry(dbIndex);
2725  if (pentry) return pentry->GetReducedAuxPlyPoints(iTable);
2726  }
2727 
2728  std::vector<float> dummy;
2729  return dummy;
2730 }
2731 
2732 bool ChartDatabase::IsChartAvailable(int dbIndex) {
2733  if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
2734  ChartTableEntry *pentry = GetpChartTableEntry(dbIndex);
2735 
2736  // If not PLugIn chart, assume always available
2737  if (pentry->GetChartType() != CHART_TYPE_PLUGIN) return true;
2738 
2739  wxString *path = pentry->GetpsFullPath();
2740  wxFileName fn(*path);
2741  wxString ext = fn.GetExt();
2742  ext.Prepend(_T("*."));
2743  wxString ext_upper = ext.MakeUpper();
2744  wxString ext_lower = ext.MakeLower();
2745 
2746  // Search the array of chart class descriptors to find a match
2747  // between the search mask and the the chart file extension
2748 
2749  for (auto &cd : m_ChartClassDescriptorArray) {
2750  if (cd.m_descriptor_type ==
2751  PLUGIN_DESCRIPTOR) {
2752  wxString search_mask = cd.m_search_mask;
2753 
2754  if (search_mask == ext_upper) {
2755  return true;
2756  }
2757  if (search_mask == ext_lower) {
2758  return true;
2759  }
2760  if (path->Matches(search_mask)) {
2761  return true;
2762  }
2763  }
2764  }
2765  }
2766 
2767  return false;
2768 }
2769 
2770 void ChartDatabase::ApplyGroupArray(ChartGroupArray *pGroupArray) {
2771  wxString separator(wxFileName::GetPathSeparator());
2772 
2773  for (unsigned int ic = 0; ic < active_chartTable.GetCount(); ic++) {
2774  ChartTableEntry *pcte = &active_chartTable[ic];
2775 
2776  pcte->ClearGroupArray();
2777 
2778  wxString *chart_full_path = pcte->GetpsFullPath();
2779 
2780  for (unsigned int igroup = 0; igroup < pGroupArray->GetCount(); igroup++) {
2781  ChartGroup *pGroup = pGroupArray->Item(igroup);
2782  for (const auto &elem : pGroup->m_element_array) {
2783  wxString element_root = elem.m_element_name;
2784 
2785  // The element may be a full single chart name
2786  // If so, add it
2787  // Otherwise, append a sep character so that similar paths are
2788  // distinguished. See FS#1060
2789  if (!chart_full_path->IsSameAs(element_root))
2790  element_root.Append(
2791  separator); // Prevent comingling similar looking path names
2792  if (chart_full_path->StartsWith(element_root)) {
2793  bool b_add = true;
2794  for (unsigned int k = 0; k < elem.m_missing_name_array.size(); k++) {
2795  const wxString &missing_item = elem.m_missing_name_array[k];
2796  if (chart_full_path->StartsWith(missing_item)) {
2797  if (chart_full_path->IsSameAs(
2798  missing_item)) // missing item is full chart name
2799  {
2800  b_add = false;
2801  break;
2802  } else {
2803  if (wxDir::Exists(missing_item)) // missing item is a dir
2804  {
2805  b_add = false;
2806  break;
2807  }
2808  }
2809  }
2810  }
2811 
2812  if (b_add) pcte->AddIntToGroupArray(igroup + 1);
2813  }
2814  }
2815  }
2816  }
2817 }
Set of basemaps at different resolutions.
Definition: Quilt.cpp:867