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