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