OpenCPN Partial API docs
Loading...
Searching...
No Matches
chartdbs.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2010 by David S. Register *
3 * Copyright (C) 2010 by Mark A Sikes *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
25#include "gl_headers.h"
26
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif
32
33#include <wx/arrimpl.cpp>
34#include <wx/dir.h>
35#include <wx/encconv.h>
36#include <wx/progdlg.h>
37#include <wx/regex.h>
38#include <wx/tokenzr.h>
39
40#include "model/gui_events.h"
41
42#include "chartbase.h"
43#include "chartdbs.h"
44#include "flex_hash.h"
45#include "LOD_reduce.h"
46#include "mbtiles.h"
47#include "navutil.h"
48#include "pluginmanager.h"
49#include "shapefile_basemap.h"
50#include "chartdb_thread.h"
51
52#ifndef UINT32
53#define UINT32 unsigned int
54#endif
55
56#ifdef __ANDROID__
57#include "androidUTIL.h"
58#endif
59
60ChartGroupArray *g_pGroupArray;
61
62static int s_dbVersion; // Database version currently in use at runtime
63 // Needed for ChartTableEntry::GetChartType() only
64 // TODO This can go away at opencpn Version 1.3.8 and
65 // above....
67
68static ChartFamilyEnum GetChartFamily(int charttype) {
69 ChartFamilyEnum cf;
70
71 switch (charttype) {
72 case CHART_TYPE_KAP:
73 cf = CHART_FAMILY_RASTER;
74 break;
75 case CHART_TYPE_GEO:
76 cf = CHART_FAMILY_RASTER;
77 break;
78 case CHART_TYPE_S57:
79 cf = CHART_FAMILY_VECTOR;
80 break;
81 case CHART_TYPE_CM93:
82 cf = CHART_FAMILY_VECTOR;
83 break;
84 case CHART_TYPE_CM93COMP:
85 cf = CHART_FAMILY_VECTOR;
86 break;
87 case CHART_TYPE_DUMMY:
88 cf = CHART_FAMILY_RASTER;
89 break;
90 case CHART_TYPE_UNKNOWN:
91 cf = CHART_FAMILY_UNKNOWN;
92 break;
93 default:
94 cf = CHART_FAMILY_UNKNOWN;
95 break;
96 }
97 return cf;
98}
99
101// ChartTableHeader
103
104void ChartTableHeader::Read(wxInputStream &is) {
105 is.Read(this, sizeof(ChartTableHeader));
106}
107
108void ChartTableHeader::Write(wxOutputStream &os) {
109 char vb[5];
110 sprintf(vb, "V%03d", DB_VERSION_CURRENT);
111
112 memcpy(dbVersion, vb, 4);
113 os.Write(this, sizeof(ChartTableHeader));
114}
115
116bool ChartTableHeader::CheckValid() {
117 char vb[5];
118 sprintf(vb, "V%03d", DB_VERSION_CURRENT);
119 if (strncmp(vb, dbVersion, sizeof(dbVersion))) {
120 wxString msg;
121 char vbo[5];
122 memcpy(vbo, dbVersion, 4);
123 vbo[4] = 0;
124 msg.Append(wxString(vbo, wxConvUTF8));
125 msg.Prepend(" Warning: found incorrect chart db version: ");
126 wxLogMessage(msg);
127
128 // return false; // no match....
129
130 // Try previous version....
131 sprintf(vb, "V%03d", DB_VERSION_PREVIOUS);
132 if (strncmp(vb, dbVersion, sizeof(dbVersion)))
133 return false;
134 else {
135 wxLogMessage(
136 " Scheduling db upgrade to current db version on "
137 "Options->Charts page visit...");
138 return true;
139 }
140
141 } else {
142 wxString msg;
143 char vbo[5];
144 memcpy(vbo, dbVersion, 4);
145 vbo[4] = 0;
146 msg.Append(wxString(vbo, wxConvUTF8));
147 msg.Prepend("Loading chart db version: ");
148 wxLogMessage(msg);
149 }
150
151 return true;
152}
153
155// ChartTableEntry
157
158void ChartTableEntry::SetScale(int scale) {
159 Scale = scale;
160 rounding = 0;
161 // XXX find the right rounding
162 if (Scale >= 1000) rounding = 5 * pow(10, log10(Scale) - 2);
163}
164
165ChartTableEntry::ChartTableEntry(ChartBase &theChart, wxString &utf8Path) {
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 SetScale(theChart.GetNativeScale());
173
174 ChartType = theChart.GetChartType();
175 ChartFamily = theChart.GetChartFamily();
176
177 Skew = theChart.GetChartSkew();
178 ProjectionType = theChart.GetChartProjectionType();
179
180 wxDateTime ed = theChart.GetEditionDate();
181 if (theChart.GetEditionDate().IsValid())
182 edition_date = theChart.GetEditionDate().GetTicks();
183
184 wxFileName fn(theChart.GetFullPath());
185 if (fn.GetModificationTime().IsValid())
186 file_date = fn.GetModificationTime().GetTicks();
187
188 m_pfilename = new wxString; // create and populate helper members
189 *m_pfilename = fn.GetFullName();
190 m_psFullPath = new wxString;
191 *m_psFullPath = utf8Path;
192 m_fullSystemPath = utf8Path;
193 m_FullPath = std::string(pFullPath);
194
195#ifdef __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),
238 &index_keep);
239 // printf("DB DP Reduction: %d/%d\n", index_keep.size(),
240 // nPlyEntries);
241
242 // Mark the keepers by adding a simple constant to ltp
243 for (unsigned int i = 0; i < index_keep.size(); i++) {
244 DPbuffer[2 * index_keep[i]] += 2000.;
245 }
246
247 float *pf = (float *)malloc(2 * index_keep.size() * sizeof(float));
248 float *pfe = pf;
249
250 for (int i = 0; i < nPlyEntries; i++) {
251 if (DPbuffer[2 * i] > 1000.) {
252 *pfe++ = DPbuffer[2 * i] - 2000.;
253 *pfe++ = DPbuffer[(2 * i) + 1];
254 }
255 }
256
257 pPlyTable = pf;
258 nPlyEntries = index_keep.size();
259 free(DPbuffer);
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),
322 &index_keep);
323 // printf("DBa DP Reduction: %d/%d\n",
324 // index_keep.size(), nPE);
325
326 // Mark the keepers by adding a simple constant to ltp
327 for (unsigned int i = 0; i < index_keep.size(); i++) {
328 DPbuffer[2 * index_keep[i]] += 2000.;
329 }
330
331 float *pf = (float *)malloc(2 * index_keep.size() * sizeof(float));
332 float *pfe1 = pf;
333
334 for (int i = 0; i < nPE; i++) {
335 if (DPbuffer[2 * i] > 1000.) {
336 *pfe1++ = DPbuffer[2 * i] - 2000.;
337 *pfe1++ = DPbuffer[(2 * i) + 1];
338 }
339 }
340
341 pft0[j] = pf;
342 pip[j] = index_keep.size();
343 free(DPbuffer);
344 } else {
345 float *pf_entry =
346 (float *)malloc(theChart.GetCOVRTablePoints(j) * 2 * sizeof(float));
347 memcpy(pf_entry, theChart.GetCOVRTableHead(j),
348 theChart.GetCOVRTablePoints(j) * 2 * sizeof(float));
349 pft0[j] = pf_entry;
350 pip[j] = theChart.GetCOVRTablePoints(j);
351 }
352 }
353
354 pAuxPlyTable = pfp;
355 pAuxCntTable = pip;
356 }
357
358 // Get and populate the NoCovr tables
359
360 nNoCovrPlyEntries = theChart.GetNoCOVREntries();
361 if (nNoCovrPlyEntries == 0) 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 =
369 (float *)malloc(theChart.GetNoCOVRTablePoints(j) * 2 * sizeof(float));
370 memcpy(pf_entry, theChart.GetNoCOVRTableHead(j),
371 theChart.GetNoCOVRTablePoints(j) * 2 * sizeof(float));
372 pft0nc[j] = pf_entry;
373 pipnc[j] = theChart.GetNoCOVRTablePoints(j);
374 }
375
376 pNoCovrPlyTable = pfpnc;
377 pNoCovrCntTable = pipnc;
378}
379
381
382ChartTableEntry::~ChartTableEntry() {
383 // free(pFullPath);
384 free(pPlyTable);
385
386 for (int i = 0; i < nAuxPlyEntries; i++) free(pAuxPlyTable[i]);
387 free(pAuxPlyTable);
388 free(pAuxCntTable);
389
390 if (nNoCovrPlyEntries) {
391 for (int i = 0; i < nNoCovrPlyEntries; i++) free(pNoCovrPlyTable[i]);
392 free(pNoCovrPlyTable);
393 free(pNoCovrCntTable);
394 }
395
396 delete m_pfilename;
397 delete m_psFullPath;
398}
399
401
403
404bool ChartTableEntry::IsEarlierThan(const ChartTableEntry &cte) const {
405 wxDateTime mine(edition_date);
406 wxDateTime theirs(cte.edition_date);
407
408 if (!mine.IsValid() || !theirs.IsValid())
409 return false; // will have the effect of keeping all questionable charts
410
411 return (mine.IsEarlierThan(theirs));
412}
413
414bool ChartTableEntry::IsEqualTo(const ChartTableEntry &cte) const {
415 wxDateTime mine(edition_date);
416 wxDateTime theirs(cte.edition_date);
417
418 if (!mine.IsValid() || !theirs.IsValid())
419 return true; // will have the effect of keeping all questionable charts
420
421 return (mine.IsEqualTo(theirs));
422}
423
425
426static int convertChartType(int charttype) {
427 // Hackeroo here....
428 // dB version 14 had different ChartType Enum, patch it here
429 if (s_dbVersion == 14) {
430 switch (charttype) {
431 case 0:
432 return CHART_TYPE_KAP;
433 case 1:
434 return CHART_TYPE_GEO;
435 case 2:
436 return CHART_TYPE_S57;
437 case 3:
438 return CHART_TYPE_CM93;
439 case 4:
440 return CHART_TYPE_CM93COMP;
441 case 5:
442 return CHART_TYPE_UNKNOWN;
443 case 6:
444 return CHART_TYPE_DONTCARE;
445 case 7:
446 return CHART_TYPE_DUMMY;
447 default:
448 return CHART_TYPE_UNKNOWN;
449 }
450 }
451 return charttype;
452}
453
454static int convertChartFamily(int charttype, int chartfamily) {
455 if (s_dbVersion < 18) {
456 switch (charttype) {
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
473bool ChartTableEntry::Read(const ChartDatabase *pDb, wxInputStream &is) {
474 char path[4096], *cp;
475
476 Clear();
477
478 // Allow reading of current db format, and maybe others
479 ChartDatabase *pD = (ChartDatabase *)pDb;
480 int db_version = pD->GetVersion();
481
482 if (db_version == 18) {
483 // Read the path first
484 for (cp = path; (*cp = (char)is.GetC()) != 0; cp++);
485 pFullPath = (char *)malloc(cp - path + 1);
486 strncpy(pFullPath, path, cp - path + 1);
487 wxLogVerbose(" Chart %s", pFullPath);
488
489 // Create and populate the helper members
490 m_pfilename = new wxString;
491 wxString fullfilename(pFullPath, wxConvUTF8);
492 wxFileName fn(fullfilename);
493 *m_pfilename = fn.GetFullName();
494 m_psFullPath = new wxString;
495 *m_psFullPath = fullfilename;
496 m_fullSystemPath = fullfilename;
497 m_FullPath = std::string(pFullPath);
498
499#ifdef __ANDROID__
500 m_fullSystemPath = wxString(fullfilename.mb_str(wxConvUTF8));
501#endif
502 // Read the table entry
504 is.Read(&cte, sizeof(ChartTableEntry_onDisk_18));
505
506 // Transcribe the elements....
507 EntryOffset = cte.EntryOffset;
508 ChartType = cte.ChartType;
509 ChartFamily = cte.ChartFamily;
510 LatMax = cte.LatMax;
511 LatMin = cte.LatMin;
512 LonMax = cte.LonMax;
513 LonMin = cte.LonMin;
514
515 m_bbox.Set(LatMin, LonMin, LatMax, LonMax);
516
517 Skew = cte.skew;
518 ProjectionType = cte.ProjectionType;
519
520 SetScale(cte.Scale);
521 edition_date = cte.edition_date;
522 file_date = cte.file_date;
523
524 nPlyEntries = cte.nPlyEntries;
525 nAuxPlyEntries = cte.nAuxPlyEntries;
526
527 nNoCovrPlyEntries = cte.nNoCovrPlyEntries;
528
529 bValid = cte.bValid;
530
531 if (nPlyEntries) {
532 int npeSize = nPlyEntries * 2 * sizeof(float);
533 pPlyTable = (float *)malloc(npeSize);
534 is.Read(pPlyTable, npeSize);
535 }
536
537 if (nAuxPlyEntries) {
538 int napeSize = nAuxPlyEntries * sizeof(int);
539 pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
540 pAuxCntTable = (int *)malloc(napeSize);
541 is.Read(pAuxCntTable, napeSize);
542
543 for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
544 nAuxPlyEntry++) {
545 int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
546 pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
547 is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
548 }
549 }
550
551 if (nNoCovrPlyEntries) {
552 int napeSize = nNoCovrPlyEntries * sizeof(int);
553 pNoCovrCntTable = (int *)malloc(napeSize);
554 is.Read(pNoCovrCntTable, napeSize);
555
556 pNoCovrPlyTable = (float **)malloc(nNoCovrPlyEntries * sizeof(float *));
557 for (int i = 0; i < nNoCovrPlyEntries; i++) {
558 int nfSize = pNoCovrCntTable[i] * 2 * sizeof(float);
559 pNoCovrPlyTable[i] = (float *)malloc(nfSize);
560 is.Read(pNoCovrPlyTable[i], nfSize);
561 }
562 }
563 }
564
565 else if (db_version == 17) {
566 // Read the path first
567 for (cp = path; (*cp = (char)is.GetC()) != 0; cp++);
568 pFullPath = (char *)malloc(cp - path + 1);
569 strncpy(pFullPath, path, cp - path + 1);
570 wxLogVerbose(" Chart %s", pFullPath);
571
572 // Create and populate the helper members
573 m_pfilename = new wxString;
574 wxString fullfilename(pFullPath, wxConvUTF8);
575 wxFileName fn(fullfilename);
576 *m_pfilename = fn.GetFullName();
577 m_psFullPath = new wxString;
578 *m_psFullPath = fullfilename;
579 m_FullPath = std::string(pFullPath);
580
581 // Read the table entry
583 is.Read(&cte, sizeof(ChartTableEntry_onDisk_17));
584
585 // Transcribe the elements....
586 EntryOffset = cte.EntryOffset;
587 ChartType = cte.ChartType;
588 LatMax = cte.LatMax;
589 LatMin = cte.LatMin;
590 LonMax = cte.LonMax;
591 LonMin = cte.LonMin;
592
593 m_bbox.Set(LatMin, LatMax, LonMin, LonMax);
594
595 Skew = cte.skew;
596 ProjectionType = cte.ProjectionType;
597
598 SetScale(cte.Scale);
599 edition_date = cte.edition_date;
600 file_date = cte.file_date;
601
602 nPlyEntries = cte.nPlyEntries;
603 nAuxPlyEntries = cte.nAuxPlyEntries;
604
605 nNoCovrPlyEntries = cte.nNoCovrPlyEntries;
606
607 bValid = cte.bValid;
608
609 if (nPlyEntries) {
610 int npeSize = nPlyEntries * 2 * sizeof(float);
611 pPlyTable = (float *)malloc(npeSize);
612 is.Read(pPlyTable, npeSize);
613 }
614
615 if (nAuxPlyEntries) {
616 int napeSize = nAuxPlyEntries * sizeof(int);
617 pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
618 pAuxCntTable = (int *)malloc(napeSize);
619 is.Read(pAuxCntTable, napeSize);
620
621 for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
622 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 // Read the path first
645 for (cp = path; (*cp = (char)is.GetC()) != 0; cp++);
646 // TODO: optimize prepended dir
647 pFullPath = (char *)malloc(cp - path + 1);
648 strncpy(pFullPath, path, cp - path + 1);
649 wxLogVerbose(" Chart %s", pFullPath);
650
651 // Create and populate the helper members
652 m_pfilename = new wxString;
653 wxString fullfilename(pFullPath, wxConvUTF8);
654 wxFileName fn(fullfilename);
655 *m_pfilename = fn.GetFullName();
656 m_psFullPath = new wxString;
657 *m_psFullPath = fullfilename;
658 m_FullPath = std::string(pFullPath);
659
660 // Read the table entry
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;
699 nAuxPlyEntry++) {
700 int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
701 pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
702 is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
703 }
704 }
705 }
706
707 else if (db_version == 15) {
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(" Chart %s", pFullPath);
714
715 // Read the table entry
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;
751 nAuxPlyEntry++) {
752 int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
753 pAuxPlyTable[nAuxPlyEntry] = (float *)malloc(nfSize);
754 is.Read(pAuxPlyTable[nAuxPlyEntry], nfSize);
755 }
756 }
757 } else if (db_version == 14) {
758 // Read the path first
759 for (cp = path; (*cp = (char)is.GetC()) != 0; cp++);
760 pFullPath = (char *)malloc(cp - path + 1);
761 strncpy(pFullPath, path, cp - path + 1);
762 wxLogVerbose(" Chart %s", pFullPath);
763
764 // Read the table entry
766 is.Read(&cte, sizeof(ChartTableEntry_onDisk_14));
767
768 // Transcribe the elements....
769 EntryOffset = cte.EntryOffset;
770 ChartType = cte.ChartType;
771 LatMax = cte.LatMax;
772 LatMin = cte.LatMin;
773 LonMax = cte.LonMax;
774 LonMin = cte.LonMin;
775
776 m_bbox.Set(LatMin, LatMax, LonMin, LonMax);
777
778 SetScale(cte.Scale);
779 edition_date = cte.edition_date;
780 file_date = 0; // file_date does not exist in V14;
781 nPlyEntries = cte.nPlyEntries;
782 nAuxPlyEntries = cte.nAuxPlyEntries;
783 bValid = cte.bValid;
784
785 if (nPlyEntries) {
786 int npeSize = nPlyEntries * 2 * sizeof(float);
787 pPlyTable = (float *)malloc(npeSize);
788 is.Read(pPlyTable, npeSize);
789 }
790
791 if (nAuxPlyEntries) {
792 int napeSize = nAuxPlyEntries * sizeof(int);
793 pAuxPlyTable = (float **)malloc(nAuxPlyEntries * sizeof(float *));
794 pAuxCntTable = (int *)malloc(napeSize);
795 is.Read(pAuxCntTable, napeSize);
796
797 for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries;
798 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
812
813bool ChartTableEntry::Write(const ChartDatabase *pDb, wxOutputStream &os) {
814 os.Write(pFullPath, strlen(pFullPath) + 1);
815
816 // Write the current version type only
817 // Create an on_disk table entry
819
820 // Transcribe the elements....
821 cte.EntryOffset = EntryOffset;
822 cte.ChartType = ChartType;
823 cte.ChartFamily = ChartFamily;
824 cte.LatMax = LatMax;
825 cte.LatMin = LatMin;
826 cte.LonMax = LonMax;
827 cte.LonMin = LonMin;
828
829 cte.Scale = Scale;
830 cte.edition_date = edition_date;
831 cte.file_date = file_date;
832
833 cte.nPlyEntries = nPlyEntries;
834 cte.nAuxPlyEntries = nAuxPlyEntries;
835
836 cte.skew = Skew;
837 cte.ProjectionType = ProjectionType;
838
839 cte.bValid = bValid;
840
841 cte.nNoCovrPlyEntries = nNoCovrPlyEntries;
842
843 os.Write(&cte, sizeof(ChartTableEntry_onDisk_18));
844 wxLogVerbose(" Wrote Chart %s", pFullPath);
845
846 // Write out the tables
847 if (nPlyEntries) {
848 int npeSize = nPlyEntries * 2 * sizeof(float);
849 os.Write(pPlyTable, npeSize);
850 }
851
852 if (nAuxPlyEntries) {
853 int napeSize = nAuxPlyEntries * sizeof(int);
854 os.Write(pAuxCntTable, napeSize);
855
856 for (int nAuxPlyEntry = 0; nAuxPlyEntry < nAuxPlyEntries; nAuxPlyEntry++) {
857 int nfSize = pAuxCntTable[nAuxPlyEntry] * 2 * sizeof(float);
858 os.Write(pAuxPlyTable[nAuxPlyEntry], nfSize);
859 }
860 }
861
862 if (nNoCovrPlyEntries) {
863 int ncSize = nNoCovrPlyEntries * sizeof(int);
864 os.Write(pNoCovrCntTable, ncSize);
865
866 for (int i = 0; i < nNoCovrPlyEntries; i++) {
867 int nctSize = pNoCovrCntTable[i] * 2 * sizeof(float);
868 os.Write(pNoCovrPlyTable[i], nctSize);
869 }
870 }
871
872 return true;
873}
874
876
877void ChartTableEntry::Clear() {
878 // memset(this, 0, sizeof(ChartTableEntry));
879
880 pFullPath = NULL;
881 pPlyTable = NULL;
882 pAuxPlyTable = NULL;
883 pAuxCntTable = NULL;
884 bValid = false;
885 ;
886 pNoCovrCntTable = NULL;
887 pNoCovrPlyTable = NULL;
888
889 nNoCovrPlyEntries = 0;
890 nAuxPlyEntries = 0;
891
892 m_pfilename = NULL; // a helper member, not on disk
893 m_psFullPath = NULL;
894}
895
897
898void ChartTableEntry::Disable() {
899 // Mark this chart in the database, so that it will not be seen during this
900 // run How? By setting the chart bounding box to an absurd value
901 // TODO... Fix this heinous hack
902 LatMax += (float)1000.;
903 LatMin += (float)1000.;
904}
905
906void ChartTableEntry::ReEnable() {
907 if (LatMax > 90.) {
908 LatMax -= (float)1000.;
909 LatMin -= (float)1000.;
910 }
911}
912bool ChartTableEntry::IsBasemap() const {
913 wxFileName fn(GetFullPath());
914 return (fn.GetPath().Lower().Contains("basemap"));
915}
916
917std::vector<float> ChartTableEntry::GetReducedPlyPoints() {
918 if (m_reducedPlyPoints.size()) return m_reducedPlyPoints;
919
920 // Reduce the LOD of the chart outline PlyPoints
921 float LOD_meters = 1;
922
923 float plylat, plylon;
924 const int nPoints = GetnPlyEntries();
925
926 float *fpo = GetpPlyTable();
927
928 double *ppd = new double[nPoints * 2];
929 double *ppsm = new double[nPoints * 2];
930 double *npr = ppd;
931 double *npsm = ppsm;
932 for (int i = 0; i < nPoints; i++) {
933 plylat = fpo[i * 2];
934 plylon = fpo[i * 2 + 1];
935
936 double x, y;
937 toSM(plylat, plylon, fpo[0], fpo[1], &x, &y);
938
939 *npr++ = plylon;
940 *npr++ = plylat;
941 *npsm++ = x;
942 *npsm++ = y;
943 }
944
945 std::vector<int> index_keep;
946 if (nPoints > 10) {
947 index_keep.push_back(0);
948 index_keep.push_back(nPoints - 1);
949 index_keep.push_back(1);
950 index_keep.push_back(nPoints - 2);
951
952 DouglasPeuckerM(ppsm, 1, nPoints - 2, LOD_meters, &index_keep);
953
954 } else {
955 index_keep.resize(nPoints);
956 for (int i = 0; i < nPoints; i++) index_keep[i] = i;
957 }
958
959 double *ppr = ppd;
960 for (int ip = 0; ip < nPoints; ip++) {
961 double x = *ppr++;
962 double y = *ppr++;
963
964 for (unsigned int j = 0; j < index_keep.size(); j++) {
965 if (index_keep[j] == ip) {
966 m_reducedPlyPoints.push_back(x);
967 m_reducedPlyPoints.push_back(y);
968 break;
969 }
970 }
971 }
972
973 delete[] ppd;
974 delete[] ppsm;
975
976 int nprr = m_reducedPlyPoints.size() / 2;
977
978 return m_reducedPlyPoints;
979}
980
981std::vector<float> ChartTableEntry::GetReducedAuxPlyPoints(int iTable) {
982 // Maybe need to initialize the vector
983 if (!m_reducedAuxPlyPointsVector.size()) {
984 std::vector<float> vec;
985 for (int i = 0; i < GetnAuxPlyEntries(); i++) {
986 m_reducedAuxPlyPointsVector.push_back(vec);
987 }
988 }
989
990 std::vector<float> vec;
991
992 // Invalid parameter
993 if ((unsigned int)iTable >= m_reducedAuxPlyPointsVector.size()) return vec;
994
995 if (m_reducedAuxPlyPointsVector.at(iTable).size())
996 return m_reducedAuxPlyPointsVector.at(iTable);
997
998 // Reduce the LOD of the chart outline PlyPoints
999 float LOD_meters = 1.0;
1000
1001 const int nPoints = GetAuxCntTableEntry(iTable);
1002 float *fpo = GetpAuxPlyTableEntry(iTable);
1003
1004 double *ppd = new double[nPoints * 2];
1005 double *ppsm = new double[nPoints * 2];
1006 double *npr = ppd;
1007 double *npsm = ppsm;
1008 float plylat, plylon;
1009
1010 for (int i = 0; i < nPoints; i++) {
1011 plylat = fpo[i * 2];
1012 plylon = fpo[i * 2 + 1];
1013
1014 double x, y;
1015 toSM(plylat, plylon, fpo[0], fpo[1], &x, &y);
1016
1017 *npr++ = plylon;
1018 *npr++ = plylat;
1019 *npsm++ = x;
1020 *npsm++ = y;
1021 }
1022
1023 std::vector<int> index_keep;
1024 if (nPoints > 10) {
1025 index_keep.push_back(0);
1026 index_keep.push_back(nPoints - 1);
1027 index_keep.push_back(1);
1028 index_keep.push_back(nPoints - 2);
1029
1030 DouglasPeuckerM(ppsm, 1, nPoints - 2, LOD_meters, &index_keep);
1031
1032 } else {
1033 index_keep.resize(nPoints);
1034 for (int i = 0; i < nPoints; i++) index_keep[i] = i;
1035 }
1036
1037 int nnn = index_keep.size();
1038
1039 double *ppr = ppd;
1040 for (int ip = 0; ip < nPoints; ip++) {
1041 double x = *ppr++;
1042 double y = *ppr++;
1043
1044 for (unsigned int j = 0; j < index_keep.size(); j++) {
1045 if (index_keep[j] == ip) {
1046 vec.push_back(x);
1047 vec.push_back(y);
1048 break;
1049 }
1050 }
1051 }
1052
1053 delete[] ppd;
1054 delete[] ppsm;
1055
1056 m_reducedAuxPlyPointsVector[iTable] = vec;
1057
1058 int nprr = vec.size() / 2;
1059
1060 return vec;
1061}
1062
1064// ChartDatabase
1066
1067WX_DEFINE_OBJARRAY(ChartTable);
1068
1069ChartDatabase::ChartDatabase() {
1070 bValid = false;
1071 SetBusy(false);
1072
1073 m_ChartTableEntryDummy.Clear();
1074
1075 Bind(wxEVT_OCPN_CHARTTABLEENTRYTHREAD, &ChartDatabase::OnEvtThread, this);
1076
1077 UpdateChartClassDescriptorArray();
1078}
1079
1080#if 0
1081// Check for duplicates within this directory
1082bool collision_found = false;
1083ChartCollisionsHashMap::iterator it;
1084for (it = collision_map.begin(); it != collision_map.end(); ++it) {
1085 if (it->first.IsSameAs(file_name)) {
1086 // Two files found with identical file name
1087 // For now, just drop this ticket
1088 // TODO Make an (expensive) test on file modification times
1089 collision_found = true;
1090 break;
1091 }
1092}
1093#endif
1094
1095void ChartDatabase::OnEvtThread(OCPN_ChartTableEntryThreadEvent &event) {
1096 // Capture the completed job tickets
1097 auto ticket = event.GetTicket();
1098
1099 // Update progress dialog, if present
1100 if (m_pprog && (m_jobsRemaining > 1)) {
1101 m_progcount++;
1102 double ratio = 100. * (double)m_progcount / m_ticketcount;
1103 int val = ratio;
1104 if (((m_progcount % m_nFileProgressQuantum) == 0)) {
1105 if (val != m_progint) {
1106 m_progint = val;
1107 // printf("%d %d\n", m_progcount, val);
1108 m_pprog->Update(val);
1109 }
1110 }
1111 }
1112
1113 // look for and process incomplete tickets
1114#if 0
1115 if (!ticket->b_thread_safe) {
1116 ChartClassDescriptor tmpDescriptor(ticket->provider_class_name, "", nullptr,
1117 PLUGIN_DESCRIPTOR);
1118 ChartTableEntry *pnewChartTableEntry = CreateChartTableEntry(
1119 ticket->m_ChartPath, ticket->m_ChartPath, tmpDescriptor);
1120 if (pnewChartTableEntry) {
1121 std::shared_ptr<ChartTableEntry> safe_ptr(pnewChartTableEntry);
1122 ticket->m_chart_table_entry = safe_ptr; // class member
1123 }
1124 }
1125#endif
1126
1127 if (ticket) {
1128 wxFileName fn(ticket->m_ChartPath);
1129 bool collision_found = false;
1130 ChartCollisionsHashMap::iterator it;
1131 for (it = m_full_collision_map.begin(); it != m_full_collision_map.end();
1132 ++it) {
1133 if (it->first.IsSameAs(fn.GetFullName())) {
1134 // Two files found with identical file name
1135 // For now, just drop this ticket
1136 // TODO Make an (expensive) test on file modification times
1137 collision_found = true;
1138 break;
1139 }
1140 }
1141
1142 // Consider TODO #1 here: Looking for more duolicates acress directories
1143
1144 if (!collision_found) {
1145 m_ticket_vector.push_back(ticket);
1146 m_full_collision_map[fn.GetFullName()] = 1;
1147 }
1148 }
1149
1150 int remaining = --m_jobsRemaining;
1151 if (remaining == 0) {
1152 m_pool.Shutdown();
1153
1154 // Process the deferred tickets
1155 for (auto &ticket_d : m_deferred_ticket_vector) {
1156 ChartClassDescriptor tmpDescriptor(ticket_d->provider_class_name, "",
1157 nullptr, PLUGIN_DESCRIPTOR);
1158 ChartTableEntry *pnewChartTableEntry = CreateChartTableEntry(
1159 ticket_d->m_ChartPath, ticket_d->m_ChartPath, tmpDescriptor);
1160 if (pnewChartTableEntry) {
1161 std::shared_ptr<ChartTableEntry> safe_ptr(pnewChartTableEntry);
1162 ticket_d->m_chart_table_entry = safe_ptr; // class member
1163 m_ticket_vector.push_back(ticket_d);
1164 }
1165 }
1166 m_deferred_ticket_vector.clear();
1167
1168 // Now m_ticket_vector is populated with verified tickets
1169 // for all specified directories.
1170
1171 // Add the CTEs from the tickets to the active table
1172 // NB: The active_chartTable was cleared by the Update() method
1173 // before this point
1174
1175 for (auto &ticket_valid : m_ticket_vector) {
1176 if (ticket_valid->m_chart_table_entry)
1177 active_chartTable.push_back(ticket_valid->m_chart_table_entry);
1178 }
1179 // Release references to tickets
1180 m_ticket_vector.clear();
1181
1182 FinalizeChartUpdate();
1183 }
1184}
1185
1186void ChartDatabase::FinalizeChartUpdate() {
1187 // Scrub CTE list, remove any anvalid entries,
1188 // as tagged by directory removal
1189 active_chartTable.erase(
1190 std::remove_if(active_chartTable.begin(), active_chartTable.end(),
1191 [](const auto &cte) { return !cte->GetbValid(); }),
1192 active_chartTable.end());
1193
1194 // And once more, setting the Entry index field
1195 active_chartTable_pathindex.clear();
1196 int i = 0;
1197 for (auto cte : active_chartTable) {
1198 active_chartTable_pathindex[cte->GetFullSystemPath()] = i;
1199 cte->SetEntryOffset(i);
1200 i++;
1201 }
1202
1203 // Touch up the group structures, as necessary
1204 ScrubGroupArray();
1205 ChartData->ApplyGroupArray(g_pGroupArray);
1206
1207 m_nentries = active_chartTable.size();
1208 bValid = true;
1209 SetBusy(false);
1210 ChartData->SetBusy(false);
1211
1212 // Finalize the dB on disk
1213 ChartData->SaveBinary(ChartListFileName);
1214 wxLogMessage("Finished chart database Update");
1215 wxLogMessage(" ");
1216
1217 if (m_pprog) m_pprog->Destroy();
1218 m_pprog = nullptr;
1219
1220 // The Update() function may set gWorldMapLocation if at least one of the
1221 // directories contains GSHHS files. Make sure GSHHS is still accessible
1222 if (gWorldMapLocation.empty()) { // Last resort. User might have deleted all
1223 // GSHHG data, but we still might have the
1224 // default dataset distributed with OpenCPN
1225 // or from the package repository...
1226 gWorldMapLocation = gDefaultWorldMapLocation;
1227 m_gshhg_chart_loc = wxEmptyString;
1228 }
1229
1230 // Update canvas and "Crossesland" machinery
1231 if (gWorldMapLocation != m_gshhg_chart_loc) {
1232 // ..For each canvas...
1233 // TODO Move this where?
1234 // for (unsigned int i = 0; i < g_canvasArray.GetCount(); i++) {
1235 // ChartCanvas *cc = g_canvasArray.Item(i);
1236 // if (cc) cc->ResetWorldBackgroundChart();
1237 // }
1238
1239 // Reset the GSHHS singleton which is used to detect land crossing.
1240 gshhsCrossesLandReset();
1241 }
1242
1243 // Purge all charts from cache, and delete.
1244 ChartData->PurgeCache();
1245
1246 // Signal Options dialog that update is finished
1247 GuiEvents::GetInstance().options_on_finalize_chartdbs.Notify();
1248
1249 // Signal a full chart reload
1250 GuiEvents::GetInstance().on_finalize_chartdbs.Notify();
1251}
1252
1253bool ChartDatabase::ScrubGroupArray() {
1254 // For each group,
1255 // make sure that each group element (dir or chart) references at least
1256 // oneitem in the database. If not, remove the element.
1257
1258 bool b_change = false;
1259 unsigned int igroup = 0;
1260 while (igroup < g_pGroupArray->GetCount()) {
1261 bool b_chart_in_element = false;
1262 ChartGroup *pGroup = g_pGroupArray->Item(igroup);
1263
1264 for (unsigned int j = 0; j < pGroup->m_element_array.size(); j++) {
1265 const wxString &element_root = pGroup->m_element_array[j].m_element_name;
1266
1267 for (unsigned int ic = 0;
1268 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1269 auto &cte = ChartData->GetChartTableEntry(ic);
1270 wxString chart_full_path = cte.GetFullSystemPath();
1271
1272 if (chart_full_path.StartsWith(element_root)) {
1273 b_chart_in_element = true;
1274 break;
1275 }
1276 }
1277
1278 // Explicit check to avoid removing a group containing only GSHHS
1279 if (!b_chart_in_element) {
1280 wxString test_string = "GSHH";
1281 if (element_root.Upper().Contains(test_string))
1282 b_chart_in_element = true;
1283 }
1284
1285 if (!b_chart_in_element) // delete the element
1286 {
1287 pGroup->m_element_array.erase(pGroup->m_element_array.begin() + j);
1288 j--;
1289 b_change = true;
1290 }
1291 }
1292
1293 igroup++; // next group
1294 }
1295
1296 return b_change;
1297}
1298
1299void ChartDatabase::UpdateChartClassDescriptorArray() {
1300 if (m_ChartClassDescriptorArray.empty()) {
1301 m_ChartClassDescriptorArray.push_back(
1302 ChartClassDescriptor("ChartKAP", "*.kap", nullptr, BUILTIN_DESCRIPTOR));
1303 m_ChartClassDescriptorArray.push_back(
1304 ChartClassDescriptor("ChartGEO", "*.geo", nullptr, BUILTIN_DESCRIPTOR));
1305 m_ChartClassDescriptorArray.push_back(
1306 ChartClassDescriptor("s57chart", "*.000", nullptr, BUILTIN_DESCRIPTOR));
1307 m_ChartClassDescriptorArray.push_back(
1308 ChartClassDescriptor("s57chart", "*.s57", nullptr, BUILTIN_DESCRIPTOR));
1309 m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(
1310 "cm93compchart", "00300000.a", nullptr, BUILTIN_DESCRIPTOR));
1311 m_ChartClassDescriptorArray.push_back(ChartClassDescriptor(
1312 "ChartMbTiles", "*.mbtiles", nullptr, BUILTIN_DESCRIPTOR));
1313 }
1314 // If the PlugIn Manager exists, get the array of dynamically loadable
1315 // chart class names
1316 if (g_pi_manager) {
1317 m_ChartClassDescriptorArray.erase(
1318 std::remove_if(m_ChartClassDescriptorArray.begin(),
1319 m_ChartClassDescriptorArray.end(),
1320 [](const ChartClassDescriptor &cd) {
1321 return cd.m_descriptor_type == PLUGIN_DESCRIPTOR;
1322 }),
1323 m_ChartClassDescriptorArray.end());
1324
1325 wxArrayString array = g_pi_manager->GetPlugInChartClassNameArray();
1326 for (unsigned int j = 0; j < array.GetCount(); j++) {
1327 // Instantiate a blank chart to retrieve the directory search mask for
1328 // this chart type
1329 wxString class_name = array[j];
1330 ChartPlugInWrapper *cpiw = new ChartPlugInWrapper(class_name);
1331 if (cpiw) {
1332 wxString mask = cpiw->GetFileSearchMask();
1333
1334 // Get the plugin (ptr) that provides this chart class
1335 auto plugin_t = g_pi_manager->GetProvidingPlugin(class_name);
1336 opencpn_plugin *plugin = dynamic_cast<opencpn_plugin *>(plugin_t);
1337 // Create a new descriptor and add it to the database
1338 m_ChartClassDescriptorArray.push_back(
1339 ChartClassDescriptor(class_name, mask, plugin, PLUGIN_DESCRIPTOR));
1340 delete cpiw;
1341 }
1342 }
1343 }
1344}
1345
1346ChartTableEntry &ChartDatabase::GetChartTableEntry(int index) const {
1347 // if (index < GetChartTableEntries())
1348 return *active_chartTable[index];
1349 // TODO fix
1350 // else
1351 // return m_ChartTableEntryDummy;
1352}
1353
1354#if 0
1355ChartTableEntry *ChartDatabase::GetpChartTableEntry(int index) const {
1356 if (index < GetChartTableEntries())
1357 return (active_chartTable[index])->get();
1358 else
1359 return (ChartTableEntry *)&m_ChartTableEntryDummy;
1360}
1361#endif
1362
1363bool ChartDatabase::CompareChartDirArray(ArrayOfCDI &test_array) {
1364 // Compare the parameter "test_array" with this.m_dir_array
1365 // Return true if functionally identical (order does not signify).
1366
1367 if (test_array.GetCount() != m_dir_array.GetCount()) return false;
1368
1369 bool bfound_inner;
1370 unsigned int nfound_outer = 0;
1371
1372 for (unsigned int i = 0; i < test_array.GetCount(); i++) {
1373 ChartDirInfo p = test_array[i];
1374 bfound_inner = false;
1375 for (unsigned int j = 0; j < m_dir_array.GetCount(); j++) {
1376 ChartDirInfo q = m_dir_array[j];
1377
1378 if (p.fullpath.IsSameAs(q.fullpath)) {
1379 bfound_inner = true;
1380 break;
1381 }
1382 }
1383 if (bfound_inner) nfound_outer++;
1384 }
1385
1386 return (nfound_outer == test_array.GetCount());
1387}
1388
1389wxString ChartDatabase::GetMagicNumberCached(wxString dir) {
1390 for (unsigned int j = 0; j < m_dir_array.GetCount(); j++) {
1391 ChartDirInfo q = m_dir_array[j];
1392 if (dir.IsSameAs(q.fullpath)) return q.magic_number;
1393 }
1394
1395 return "";
1396}
1397
1398bool ChartDatabase::Read(const wxString &filePath) {
1399 ChartTableEntry entry;
1400 int entries;
1401
1402 bValid = false;
1403
1404 wxFileName file(filePath);
1405 if (!file.FileExists()) return false;
1406
1407 m_DBFileName = filePath;
1408
1409 wxFFileInputStream ifs(filePath);
1410 if (!ifs.Ok()) return false;
1411
1412 ChartTableHeader cth;
1413 cth.Read(ifs);
1414 if (!cth.CheckValid()) return false;
1415
1416 // Capture the version number
1417 char vbo[5];
1418 memcpy(vbo, cth.GetDBVersionString(), 4);
1419 vbo[4] = 0;
1420 m_dbversion = atoi(&vbo[1]);
1421 s_dbVersion = m_dbversion; // save the static copy
1422
1423 wxLogVerbose("Chartdb:Reading %d directory entries, %d table entries",
1424 cth.GetDirEntries(), cth.GetTableEntries());
1425 wxLogMessage("Chartdb: Chart directory list follows");
1426 if (0 == cth.GetDirEntries()) wxLogMessage(" Nil");
1427
1428 int ind = 0;
1429 for (int iDir = 0; iDir < cth.GetDirEntries(); iDir++) {
1430 wxString dir;
1431 int dirlen;
1432 ifs.Read(&dirlen, sizeof(int));
1433 while (dirlen > 0) {
1434 char dirbuf[1024];
1435 int alen = dirlen > 1023 ? 1023 : dirlen;
1436 if (ifs.Read(&dirbuf, alen).Eof()) goto read_error;
1437 dirbuf[alen] = 0;
1438 dirlen -= alen;
1439 dir.Append(wxString(dirbuf, wxConvUTF8));
1440 }
1441 wxString msg;
1442 msg.Printf(" Chart directory #%d: ", iDir);
1443 msg.Append(dir);
1444 wxLogMessage(msg);
1445 m_chartDirs.Add(dir);
1446 }
1447
1448 entries = cth.GetTableEntries();
1449 // active_chartTable.Alloc(entries);
1450 active_chartTable_pathindex.clear();
1451 while (entries-- && entry.Read(this, ifs)) {
1452 active_chartTable_pathindex[entry.GetFullSystemPath()] = ind++;
1453 auto sharedPtr = std::make_shared<ChartTableEntry>(entry);
1454 active_chartTable.push_back(sharedPtr);
1455 }
1456
1457 entry.Clear();
1458 bValid = true;
1459 entry.SetAvailable(true);
1460
1461 m_nentries = active_chartTable.size();
1462 return true;
1463
1464read_error:
1465 bValid = false;
1466 m_nentries = active_chartTable.size();
1467 return false;
1468}
1469
1471
1472bool ChartDatabase::Write(const wxString &filePath) {
1473 wxFileName file(filePath);
1474 wxFileName dir(
1475 file.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME, wxPATH_NATIVE));
1476
1477 if (!dir.DirExists() && !dir.Mkdir()) return false;
1478
1479 wxFFileOutputStream ofs(filePath);
1480 if (!ofs.Ok()) return false;
1481
1482 ChartTableHeader cth(m_chartDirs.GetCount(), active_chartTable.size());
1483 cth.Write(ofs);
1484
1485 for (int iDir = 0; iDir < cth.GetDirEntries(); iDir++) {
1486 wxString dir1 = m_chartDirs[iDir];
1487 int dirlen = dir1.length();
1488 char s[200];
1489 strncpy(s, dir1.mb_str(wxConvUTF8), 199);
1490 s[199] = 0;
1491 dirlen = strlen(s);
1492 ofs.Write(&dirlen, sizeof(int));
1493 // ofs.Write(dir.fn_str(), dirlen);
1494 ofs.Write(s, dirlen);
1495 }
1496
1497 for (UINT32 iTable = 0; iTable < active_chartTable.size(); iTable++) {
1498 auto &cte = GetChartTableEntry(iTable);
1499 cte.Write(this, ofs);
1500 }
1501
1502 // Explicitly set the version
1503 m_dbversion = DB_VERSION_CURRENT;
1504
1505 return true;
1506}
1507
1509wxString SplitPath(wxString s, wxString tkd, int nchar, int offset,
1510 int *pn_split) {
1511 wxString r;
1512 int ncr = 0;
1513
1514 int rlen = offset;
1515 wxStringTokenizer tkz(s, tkd);
1516 while (tkz.HasMoreTokens()) {
1517 wxString token = tkz.GetNextToken();
1518 if ((rlen + (int)token.Len() + 1) < nchar) {
1519 r += token;
1520 r += tkd[0];
1521 rlen += token.Len() + 1;
1522 } else {
1523 r += "\n";
1524 ncr++;
1525 for (int i = 0; i < offset; i++) {
1526 r += " ";
1527 }
1528 r += token;
1529 r += tkd[0];
1530 rlen = offset + token.Len() + 1;
1531 }
1532 }
1533
1534 if (pn_split) *pn_split = ncr;
1535
1536 return r.Mid(0, r.Len() - 1); // strip the last separator char
1537}
1538
1539wxString ChartDatabase::GetFullChartInfo(ChartBase *pc, int dbIndex,
1540 int *char_width, int *line_count) {
1541 wxString r;
1542 int lc = 0;
1543 unsigned int max_width = 0;
1544 int ncr;
1545 unsigned int target_width = 60;
1546
1547 const ChartTableEntry &cte = GetChartTableEntry(dbIndex);
1548 wxString line;
1549 line.Empty();
1550 if (pc) {
1551 line = _(" Name: ");
1552 wxString longline = pc->GetName();
1553
1554 wxString tkz;
1555 if (longline.Find(' ') != wxNOT_FOUND) // assume a proper name
1556 tkz = " ";
1557 else
1558 tkz = "/,\\"; // else a file name
1559
1560 if (longline.Len() > target_width) {
1561 line += SplitPath(pc->GetName(), tkz, target_width, 12, &ncr);
1562 max_width = wxMax(max_width, target_width + 4);
1563 lc += ncr;
1564 } else {
1565 line += longline;
1566 max_width = wxMax(max_width, line.Len() + 4);
1567 }
1568 }
1569
1570 line += "\n";
1571 r += line;
1572 lc++;
1573
1574 if (pc) // chart is loaded and available
1575 line.Printf(" %s: 1:%d", _("Scale"), pc->GetNativeScale());
1576 else
1577 line.Printf(" %s: 1:%d", _("Scale"), cte.GetScale());
1578
1579 line += "\n";
1580 max_width = wxMax(max_width, line.Len());
1581 r += line;
1582 lc++;
1583 if (pc) {
1584 wxDateTime ed = pc->GetEditionDate();
1585 if (ed.IsValid()) {
1586 line = _(" Updated: ") + ed.FormatISODate() + "\n";
1587 max_width = wxMax(max_width, line.Len());
1588 r += line;
1589 }
1590 lc++;
1591
1592 line = _(" Source Edition: ") + pc->GetSE() + "\n";
1593 max_width = wxMax(max_width, line.Len());
1594 r += line;
1595 lc++;
1596 }
1597
1598 if (pc) {
1599 line = _(" Depth Units: ") + pc->GetDepthUnits() + "\n";
1600 max_width = wxMax(max_width, line.Len());
1601 r += line;
1602 lc++;
1603
1604 line = _(" Soundings: ") + pc->GetSoundingsDatum() + "\n";
1605 max_width = wxMax(max_width, line.Len());
1606 r += line;
1607 lc++;
1608
1609 line = _(" Datum: ") + pc->GetDatumString() + "\n";
1610 max_width = wxMax(max_width, line.Len());
1611 r += line;
1612 lc++;
1613 }
1614
1615 line = _(" Projection: ");
1616 if (PROJECTION_UNKNOWN == cte.GetChartProjectionType())
1617 line += _("Unknown");
1618 else if (PROJECTION_MERCATOR == cte.GetChartProjectionType())
1619 line += _("Mercator");
1620 else if (PROJECTION_TRANSVERSE_MERCATOR == cte.GetChartProjectionType())
1621 line += _("Transverse Mercator");
1622 else if (PROJECTION_POLYCONIC == cte.GetChartProjectionType())
1623 line += _("Polyconic");
1624 else if (PROJECTION_WEB_MERCATOR == cte.GetChartProjectionType())
1625 line += _("Web Mercator (EPSG:3857)");
1626 line += "\n";
1627 max_width = wxMax(max_width, line.Len());
1628 r += line;
1629 lc++;
1630
1631 line.Empty();
1632 if (pc) {
1633 }
1634
1635 line.Empty();
1636 if (pc && pc->GetExtraInfo().Len()) {
1637 line += pc->GetExtraInfo();
1638 line += "\n";
1639 max_width = wxMax(max_width, line.Len());
1640 r += line;
1641 lc++;
1642 }
1643 if (pc) {
1644 line.Empty();
1645 line = _(" ID: ");
1646 line += pc->GetID();
1647 line += "\n";
1648 max_width = wxMax(max_width, line.Len());
1649 r += line;
1650 lc++;
1651 }
1652
1653 line = _(" ChartFile: ");
1654 wxString longline = *(cte.GetpsFullPath());
1655 if (longline.Len() > target_width) {
1656 line += SplitPath(longline, "/,\\", target_width, 15, &ncr);
1657 max_width = wxMax(max_width, target_width + 4);
1658 lc += ncr;
1659 } else {
1660 line += longline;
1661 max_width = wxMax(max_width, line.Len() + 4);
1662 }
1663 r += line;
1664 r += "\n";
1665 lc++;
1666
1667 if (line_count) *line_count = lc;
1668
1669 if (char_width) *char_width = max_width;
1670
1671 return r;
1672}
1673
1674// ----------------------------------------------------------------------------
1675// Create Chart Table Database by directory search
1676// resulting in valid pChartTable in (this)
1677// ----------------------------------------------------------------------------
1678bool ChartDatabase::Create(ArrayOfCDI &dir_array,
1679 wxGenericProgressDialog *pprog) {
1680 m_dir_array = dir_array;
1681
1682 bValid = false;
1683
1684 m_chartDirs.Clear();
1685 active_chartTable.clear();
1686 active_chartTable_pathindex.clear();
1687
1688 Update(dir_array, true, pprog); // force the update the reload everything
1689
1690 bValid = true;
1691
1692 // Explicitly set the version
1693 m_dbversion = DB_VERSION_CURRENT;
1694
1695 return true;
1696}
1697
1698/*
1699 * Traverse a directory recursively and find the GSHHG directory
1700 * that contains GSHHG data files.
1701 */
1702class GshhsTraverser : public wxDirTraverser {
1703public:
1704 GshhsTraverser() {}
1705 virtual wxDirTraverseResult OnFile(const wxString &filename) override {
1706 wxFileName fn(filename);
1707 wxFileName dir(fn.GetPath());
1708 if (fn.GetFullName().Matches("poly-*-1.dat") &&
1709 dir.GetFullName().IsSameAs("GSHHG", false)) {
1710 parent_dir = fn.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
1711 return wxDIR_STOP;
1712 }
1713 return wxDIR_CONTINUE;
1714 }
1715 virtual wxDirTraverseResult OnDir(const wxString &dirname) override {
1716 // Always recurse into directories.
1717 return wxDIR_CONTINUE;
1718 }
1719 wxString GetGshhsDir() const { return parent_dir; }
1720
1721private:
1722 wxString parent_dir;
1723};
1724
1725/*
1726 * Find and return the full path a of directory containing GSHHG data files.
1727 * Search recursively starting from directory.
1728 */
1729wxString findGshhgDirectory(const wxString &directory) {
1730 wxDir dir(directory);
1731 if (!dir.IsOpened()) {
1732 return wxEmptyString;
1733 }
1734 GshhsTraverser traverser;
1735 dir.Traverse(traverser, wxEmptyString, wxDIR_FILES | wxDIR_DIRS);
1736 return traverser.GetGshhsDir();
1737}
1738
1739bool ChartDatabase::UpdateChartDatabaseInplace(ArrayOfCDI &DirArray,
1740 bool b_force, bool b_prog) {
1741 // AbstractPlatform::ShowBusySpinner();
1742
1743 if (b_prog) {
1744 wxString longmsg = _("OpenCPN Chart Update");
1745 longmsg +=
1746 ".................................................................."
1747 "........";
1748
1749 m_pprog = new wxGenericProgressDialog();
1750
1751 wxFont *qFont = GetOCPNScaledFont(_("Dialog"));
1752 m_pprog->SetFont(*qFont);
1753
1754 m_pprog->Create(_("OpenCPN Chart Update"), longmsg, 100, nullptr,
1755 wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME |
1756 wxPD_REMAINING_TIME);
1757
1758 DimeControl(m_pprog);
1759 m_pprog->Show();
1760 }
1761
1762 wxLogMessage(" ");
1763 wxLogMessage("Starting chart database Update...");
1764
1765 // The Update() function may set gWorldMapLocation if at least one of the
1766 // directories contains GSHHS files. Save current situation...
1767 m_gshhg_chart_loc = gWorldMapLocation;
1768 gWorldMapLocation = wxEmptyString;
1769
1770 Update(DirArray, b_force, m_pprog);
1771
1772 // AbstractPlatform::HideBusySpinner();
1773 return true;
1774}
1775
1776// ----------------------------------------------------------------------------
1777// Update existing ChartTable Database by directory search
1778// resulting in valid pChartTable in (this)
1779// ----------------------------------------------------------------------------
1780bool ChartDatabase::Update(ArrayOfCDI &dir_array, bool bForce,
1781 wxGenericProgressDialog *pprog) {
1782 m_pprog = pprog;
1783 m_ticket_vector.clear();
1784 m_full_collision_map.clear();
1785 m_jobsRemaining = 0;
1786 m_deferred_ticket_vector.clear();
1787 m_dir_array = dir_array;
1788
1789 bValid = false; // database is not useable right now...
1790 SetBusy(true);
1791 ChartData->SetBusy(true);
1792
1793 // Mark all charts provisionally invalid
1794 for (unsigned int i = 0; i < active_chartTable.size(); i++) {
1795 auto &cte = GetChartTableEntry(i);
1796 cte.SetValid(false);
1797 }
1798
1799 m_chartDirs.Clear();
1800
1801 if (bForce) active_chartTable.clear();
1802
1803 bool lbForce = bForce;
1804
1805 // Do a dB Version upgrade if the current one is obsolete
1806 if (s_dbVersion != DB_VERSION_CURRENT) {
1807 active_chartTable.clear();
1808 lbForce = true;
1809 s_dbVersion = DB_VERSION_CURRENT; // Update the static indicator
1810 m_dbversion = DB_VERSION_CURRENT; // and the member
1811 }
1812
1813 // Get the new charts
1814
1815 for (unsigned int j = 0; j < dir_array.GetCount(); j++) {
1816 ChartDirInfo dir_info = dir_array[j];
1817
1818 // On Android, with SDK >= 30, traversal of a folder that is
1819 // on within the "scoped storage" domain is very slow.
1820 // Aviod it....
1821#ifdef __ANDROID__
1822 if (!androidIsDirWritable(dir_info.fullpath)) continue;
1823#endif
1824
1825 wxString dir_magic;
1826 // Recursively search for a directory that contains GSHHG files starting
1827 // from dir_info.
1828 wxString gshhg_dir = findGshhgDirectory(dir_info.fullpath);
1829 if (!gshhg_dir.empty()) {
1830 // If some polygons exist in the directory, set it as the one to use for
1831 // GSHHG
1832 // TODO: We should probably compare the version and maybe resolutions
1833 // available with what is currently used...
1834 wxLogMessage("Updating GSHHG directory: %s", gshhg_dir.c_str());
1835 gWorldMapLocation = gshhg_dir;
1836 }
1837 if (dir_info.fullpath.Find("OSMSHP") != wxNOT_FOUND) {
1838 if (!wxDir::FindFirst(dir_info.fullpath, "basemap_*.shp").empty()) {
1839 gWorldShapefileLocation =
1840 dir_info.fullpath + wxFileName::GetPathSeparator();
1841 gShapeBasemap.Reset();
1842 }
1843 }
1844
1845 TraverseDirAndAddCharts(dir_info, pprog, dir_magic, lbForce);
1846
1847 // Update the dir_list entry, even if the magic values are the same
1848 dir_info.magic_number = dir_magic;
1849 dir_array.RemoveAt(j);
1850 dir_array.Insert(dir_info, j);
1851
1852 m_chartDirs.Add(dir_info.fullpath);
1853 } // for
1854
1855 // Special case, if chart dir list is truly empty
1856 if (m_chartDirs.IsEmpty() || !m_jobsRemaining) {
1857 if (m_deferred_ticket_vector.size()) {
1858 auto *evt =
1859 new OCPN_ChartTableEntryThreadEvent(wxEVT_OCPN_CHARTTABLEENTRYTHREAD);
1860 evt->SetTicket(nullptr); // dummy event to get one pass of event handler
1861 m_jobsRemaining = 1;
1862 wxQueueEvent(this, evt);
1863 } else {
1864 FinalizeChartUpdate();
1865 }
1866 return true;
1867 }
1868
1869 // Initialize progress dialog
1870 m_progcount = 0;
1871 m_progint = 0;
1872 m_ticketcount = m_jobsRemaining;
1873 m_nFileProgressQuantum = wxMax(m_ticketcount / 10, 2);
1874 if (pprog) pprog->Update(0, _("Processing charts."));
1875
1876 // Start up the queued threads
1877 const int workerCount = 4;
1878
1879 if (m_pool.GetWorkerCount() < workerCount) {
1880 int threads_needed = workerCount - m_pool.GetWorkerCount();
1881 for (int i = 0; i < threads_needed; ++i) {
1882 (new PoolWorkerThread(m_pool, this))->Run();
1883 m_pool.AddWorker();
1884 }
1885 }
1886
1887 return true;
1888}
1889
1890//-------------------------------------------------------------------
1891// Find Chart dbIndex
1892//-------------------------------------------------------------------
1893
1894int ChartDatabase::FinddbIndex(wxString PathToFind) {
1895#if 0
1896 // Find the chart
1897 for(unsigned int i=0 ; i<active_chartTable.GetCount() ; i++)
1898 {
1899 if(active_chartTable[i].GetpsFullPath()->IsSameAs(PathToFind))
1900 {
1901 return i;
1902 }
1903 }
1904#else
1905 if (active_chartTable_pathindex.find(PathToFind) !=
1906 active_chartTable_pathindex.end())
1907 return active_chartTable_pathindex[PathToFind];
1908#endif
1909
1910 return -1;
1911}
1912
1913//-------------------------------------------------------------------
1914// Disable Chart
1915//-------------------------------------------------------------------
1916
1917int ChartDatabase::DisableChart(wxString &PathToDisable) {
1918 int index = FinddbIndex(PathToDisable);
1919 if (index != -1) {
1920 auto &entry = GetChartTableEntry(index);
1921 entry.Disable();
1922 return 1;
1923 }
1924 return 0;
1925}
1926
1927// ----------------------------------------------------------------------------
1928// Traverse the given directory looking for charts
1929// If bupdate is true, also search the existing database for a name match.
1930// If target chart is already in database, mark the entry valid and skip
1931// additional processing
1932// ----------------------------------------------------------------------------
1933
1934int ChartDatabase::TraverseDirAndAddCharts(ChartDirInfo &dir_info,
1935 wxGenericProgressDialog *pprog,
1936 wxString &dir_magic, bool bForce) {
1937 // Extract the true dir name and magic number from the compound string
1938 wxString dir_path = dir_info.fullpath;
1939#ifdef __ANDROID__
1940 dir_path = wxString(dir_info.fullpath.mb_str(wxConvUTF8));
1941#endif
1942
1943 wxString old_magic = dir_info.magic_number;
1944 wxString new_magic = old_magic;
1945 dir_magic = old_magic; // provisionally the same
1946
1947 int nAdd = 0;
1948
1949 bool b_skipDetectDirChange = false;
1950 bool b_dirchange = false;
1951
1952 // Does this directory actually exist?
1953 if (!wxDir::Exists(dir_path)) return 0;
1954
1955 // Check to see if this is a cm93 directory root
1956 // If so, skip the DetectDirChange since it may be very slow
1957 // and give no information
1958 // Assume a change has happened, and process accordingly
1959 bool b_cm93 = Check_CM93_Structure(dir_path);
1960 if (b_cm93) {
1961 b_skipDetectDirChange = true;
1962 b_dirchange = true;
1963 }
1964
1965 // Quick scan the directory to see if it has changed
1966 // If not, there is no need to scan again.....
1967 if (!b_skipDetectDirChange)
1968 b_dirchange = DetectDirChange(dir_path, dir_info.fullpath, old_magic,
1969 new_magic, pprog);
1970
1971 if (!bForce && !b_dirchange) {
1972 wxString msg(" No change detected on directory ");
1973 msg.Append(dir_path);
1974 wxLogMessage(msg);
1975
1976 // Traverse the database, and mark as valid all charts coming from this
1977 // dir, or anywhere in its tree
1978
1979 wxFileName fn_dir(dir_path, "stuff");
1980 unsigned int dir_path_count = fn_dir.GetDirCount();
1981
1982 if (pprog) pprog->SetTitle(_("OpenCPN Chart Scan...."));
1983
1984 int nEntries = active_chartTable.size();
1985
1986 for (int ic = 0; ic < nEntries; ic++) {
1987 auto &cte = GetChartTableEntry(ic);
1988 wxFileName fn(cte.GetFullSystemPath());
1989
1990 while (fn.GetDirCount() >= dir_path_count) {
1991 if (fn.GetPath() == dir_path) {
1992 auto &cte_a = GetChartTableEntry(ic);
1993 cte_a.SetValid(true);
1994 // if(pprog)
1995 // pprog->Update((ic * 100)
1996 // /nEntries, fn.GetFullPath());
1997
1998 break;
1999 }
2000 fn.RemoveLastDir();
2001 }
2002 }
2003
2004 return 0;
2005 }
2006
2007 // There presumably was a change in the directory contents. Return the new
2008 // magic number
2009 dir_magic = new_magic;
2010
2011 // Look for all possible defined chart classes
2012 for (auto &cd : m_ChartClassDescriptorArray) {
2013 nAdd += SearchDirAndAddCharts(dir_info.fullpath, cd, pprog);
2014 }
2015
2016 return nAdd;
2017}
2018
2019bool ChartDatabase::DetectDirChange(const wxString &dir_path,
2020 const wxString &prog_label,
2021 const wxString &magic, wxString &new_magic,
2022 wxGenericProgressDialog *pprog) {
2023 if (pprog) pprog->SetTitle(_("OpenCPN Directory Scan...."));
2024
2025 // parse the magic number
2026 long long unsigned int nmagic;
2027 wxULongLong nacc = 0;
2028
2029 magic.ToULongLong(&nmagic, 10);
2030
2031 // Get an arraystring of all files
2032 wxArrayString FileList;
2033 wxDir dir(dir_path);
2034 int n_files = dir.GetAllFiles(dir_path, &FileList);
2035 FileList.Sort(); // Ensure persistent order of items being hashed.
2036
2037 FlexHash hash(sizeof nacc);
2038 hash.Reset();
2039
2040 if (pprog) pprog->Update(0, prog_label);
2041
2042 // Traverse the list of files, getting their interesting stuff to add to
2043 // accumulator
2044 for (int ifile = 0; ifile < n_files; ifile++) {
2045 wxFileName file(FileList[ifile]);
2046
2047 // NOTE. Do not ever try to optimize this code by combining `wxString`
2048 // calls. Otherwise `fileNameUTF8` will point to a stale buffer overwritten
2049 // by garbage.
2050 wxString fileNameNative = file.GetFullPath();
2051 wxScopedCharBuffer fileNameUTF8 = fileNameNative.ToUTF8();
2052 hash.Update(fileNameUTF8.data(), fileNameUTF8.length());
2053
2054 // File Size;
2055 wxULongLong size = file.GetSize();
2056 wxULongLong fileSize = ((size != wxInvalidSize) ? size : 0);
2057 hash.Update(&fileSize, (sizeof fileSize));
2058
2059 // Mod time, in ticks
2060 wxDateTime t = file.GetModificationTime();
2061 wxULongLong fileTime = t.GetTicks();
2062 hash.Update(&fileTime, (sizeof fileTime));
2063 }
2064
2065 hash.Finish();
2066 hash.Receive(&nacc);
2067
2068 // Return the calculated magic number
2069 new_magic = nacc.ToString();
2070
2071 // And do the test
2072 if (new_magic != magic)
2073 return true;
2074 else
2075 return false;
2076}
2077
2078bool ChartDatabase::IsChartDirUsed(const wxString &theDir) {
2079 wxString dir(theDir);
2080 if (dir.Last() == '/' || dir.Last() == wxFileName::GetPathSeparator())
2081 dir.RemoveLast();
2082
2083 dir.Append("*");
2084 for (UINT32 i = 0; i < active_chartTable.size(); i++) {
2085 auto &cte_u = GetChartTableEntry(i);
2086 if (cte_u.GetpsFullPath()->Matches(dir)) return true;
2087 }
2088 return false;
2089}
2090
2091//-----------------------------------------------------------------------------
2092// Validate a given directory as a cm93 root database
2093// If it appears to be a cm93 database, then return true
2094//-----------------------------------------------------------------------------
2095bool ChartDatabase::Check_CM93_Structure(wxString dir_name) {
2096 wxString filespec;
2097
2098 wxRegEx test("[0-9]+");
2099
2100 wxDir dirt(dir_name);
2101 wxString candidate;
2102
2103 if (dirt.IsOpened())
2104 wxLogMessage("check_cm93 opened dir OK: " + dir_name);
2105 else {
2106 wxLogMessage("check_cm93 NOT OPENED OK: " + dir_name);
2107 wxLogMessage("check_cm93 returns false." + dir_name);
2108 return false;
2109 }
2110
2111 bool b_maybe_found_cm93 = false;
2112 bool b_cont = dirt.GetFirst(&candidate);
2113
2114 while (b_cont) {
2115 if (test.Matches(candidate) && (candidate.Len() == 8)) {
2116 b_maybe_found_cm93 = true;
2117 break;
2118 }
2119
2120 b_cont = dirt.GetNext(&candidate);
2121 }
2122
2123 if (b_maybe_found_cm93) {
2124 wxString dir_next = dir_name;
2125 dir_next += "/";
2126 dir_next += candidate;
2127 if (wxDir::Exists(dir_next)) {
2128 wxDir dir_n(dir_next);
2129 if (dirt.IsOpened()) {
2130 wxString candidate_n;
2131
2132 wxRegEx test_n("^[A-Ga-g]");
2133 bool b_probably_found_cm93 = false;
2134 bool b_cont_n = dir_n.IsOpened() && dir_n.GetFirst(&candidate_n);
2135 while (b_cont_n) {
2136 if (test_n.Matches(candidate_n) && (candidate_n.Len() == 1)) {
2137 b_probably_found_cm93 = true;
2138 break;
2139 }
2140 b_cont_n = dir_n.GetNext(&candidate_n);
2141 }
2142
2143 if (b_probably_found_cm93) // found a directory that looks
2144 // like {dir_name}/12345678/A
2145 // probably cm93
2146 {
2147 // make sure the dir exists
2148 wxString dir_luk = dir_next;
2149 dir_luk += "/";
2150 dir_luk += candidate_n;
2151 if (wxDir::Exists(dir_luk)) return true;
2152 }
2153 }
2154 }
2155 }
2156
2157 return false;
2158}
2159
2160/*
2161//-----------------------------------------------------------------------------
2162// Validate a given directory as a cm93 root database
2163// If it appears to be a cm93 database, then return the name of an existing cell
2164file
2165// File name will be unique with respect to member element m_cm93_filename_array
2166// If not cm93, return empty string
2167//-----------------------------------------------------------------------------
2168wxString ChartDatabase::Get_CM93_FileName(wxString dir_name)
2169{
2170 wxString filespec;
2171
2172 wxRegEx test("[0-9]+");
2173
2174 wxDir dirt(dir_name);
2175 wxString candidate;
2176
2177 bool b_maybe_found_cm93 = false;
2178 bool b_cont = dirt.GetFirst(&candidate);
2179
2180 while(b_cont)
2181 {
2182 if(test.Matches(candidate)&& (candidate.Len() == 8))
2183 {
2184 b_maybe_found_cm93 = true;
2185 break;
2186 }
2187
2188 b_cont = dirt.GetNext(&candidate);
2189
2190 }
2191
2192 if(b_maybe_found_cm93)
2193 {
2194 wxString dir_next = dir_name;
2195 dir_next += "/";
2196 dir_next += candidate;
2197 if(wxDir::Exists(dir_next))
2198 {
2199 wxDir dir_n(dir_next);
2200 wxString candidate_n;
2201
2202 wxRegEx test_n("^[A-Ga-g]");
2203 bool b_probably_found_cm93 = false;
2204 bool b_cont_n = dir_n.GetFirst(&candidate_n);
2205 while(b_cont_n)
2206 {
2207 if(test_n.Matches(candidate_n) && (candidate_n.Len() ==
22081))
2209 {
2210 b_probably_found_cm93 = true;
2211 break;
2212 }
2213 b_cont_n = dir_n.GetNext(&candidate_n);
2214 }
2215
2216 if(b_probably_found_cm93) // found a directory that
2217looks like {dir_name}/12345678/A probably cm93 { // and we want to try and
2218shorten the recursive search
2219 // make sure the dir exists
2220 wxString dir_luk = dir_next;
2221 dir_luk += "/";
2222 dir_luk += candidate_n;
2223 if(wxDir::Exists(dir_luk))
2224 {
2225 wxString msg("Found probable CM93 database in
2226"); msg += dir_name; wxLogMessage(msg);
2227
2228 wxString dir_name_plus = dir_luk; // be very
2229specific about the dir_name,
2230
2231 wxDir dir_get(dir_name_plus);
2232 wxString one_file;
2233 dir_get.GetFirst(&one_file);
2234
2235 // We must return a unique file name, i.e. one
2236that has not bee seen
2237 // before in this invocation of chart dir
2238scans. bool find_unique = false; while(!find_unique)
2239 {
2240 find_unique = true;
2241 for(unsigned int ifile=0; ifile <
2242m_cm93_filename_array.GetCount(); ifile++)
2243 {
2244 if(m_cm93_filename_array[ifile] ==
2245one_file) find_unique = false;
2246 }
2247 if(!find_unique)
2248 dir_get.GetNext(&one_file);
2249 }
2250
2251 m_cm93_filename_array.Add(one_file);
2252
2253 filespec = one_file;
2254 }
2255
2256 }
2257 }
2258 }
2259
2260 return filespec;
2261}
2262*/
2263
2264// ----------------------------------------------------------------------------
2265// Populate Chart Table by directory search for specified file type
2266// If bupdate flag is true, search the Chart Table for matching chart.
2267// if target chart is already in table, mark it valid and skip chart processing
2268// ----------------------------------------------------------------------------
2269
2270#if 0
2271int ChartDatabase::SearchDirAndAddCharts(wxString &dir_name_base,
2272 ChartClassDescriptor &chart_desc,
2273 wxGenericProgressDialog *pprog) {
2274 wxString msg("Searching directory: ");
2275 msg += dir_name_base;
2276 msg += " for ";
2277 msg += chart_desc.m_search_mask;
2278 wxLogMessage(msg);
2279
2280 wxString dir_name = dir_name_base;
2281
2282#ifdef __ANDROID__
2283 dir_name = wxString(dir_name_base.mb_str(wxConvUTF8)); // android
2284#endif
2285
2286 if (!wxDir::Exists(dir_name)) return 0;
2287
2288 wxString filespec = chart_desc.m_search_mask.Upper();
2289 wxString lowerFileSpec = chart_desc.m_search_mask.Lower();
2290 wxString filespecXZ = filespec + ".xz";
2291 wxString lowerFileSpecXZ = lowerFileSpec + ".xz";
2292 wxString filename;
2293
2294 // Count the files
2295 wxArrayString FileList;
2296 int gaf_flags = wxDIR_DEFAULT; // as default, recurse into subdirs
2297
2298 // Here is an optimization for MSW/cm93 especially
2299 // If this directory seems to be a cm93, and we are not explicitely looking
2300 // for cm93, then abort Otherwise, we will be looking thru entire cm93 tree
2301 // for non-existent .KAP files, etc.
2302
2303 bool b_found_cm93 = false;
2304 bool b_cm93 = Check_CM93_Structure(dir_name);
2305 if (b_cm93) {
2306 if (filespec != "00300000.A")
2307 return false;
2308 else {
2309 filespec = dir_name;
2310 b_found_cm93 = true;
2311 }
2312 }
2313
2314 if (!b_found_cm93) {
2315 wxDir dir(dir_name);
2316 dir.GetAllFiles(dir_name, &FileList, filespec, gaf_flags);
2317
2318#ifdef __ANDROID__
2319 if (!FileList.GetCount()) {
2320 wxArrayString afl = androidTraverseDir(dir_name, filespec);
2321 for (wxArrayString::const_iterator item = afl.begin(); item != afl.end();
2322 item++)
2323 FileList.Add(*item);
2324 }
2325#endif
2326
2327#ifndef __WXMSW__
2328 if (filespec != lowerFileSpec) {
2329 // add lowercase filespec files too
2330 wxArrayString lowerFileList;
2331 dir.GetAllFiles(dir_name, &lowerFileList, lowerFileSpec, gaf_flags);
2332
2333#ifdef __ANDROID__
2334 if (!lowerFileList.GetCount()) {
2335 wxArrayString afl = androidTraverseDir(dir_name, lowerFileSpec);
2336 for (wxArrayString::const_iterator item = afl.begin();
2337 item != afl.end(); item++)
2338 lowerFileList.Add(*item);
2339 }
2340#endif
2341
2342 for (wxArrayString::const_iterator item = lowerFileList.begin();
2343 item != lowerFileList.end(); item++)
2344 FileList.Add(*item);
2345 }
2346#endif
2347
2348#ifdef OCPN_USE_LZMA
2349 // add xz compressed files;
2350 dir.GetAllFiles(dir_name, &FileList, filespecXZ, gaf_flags);
2351 dir.GetAllFiles(dir_name, &FileList, lowerFileSpecXZ, gaf_flags);
2352#endif
2353
2354 FileList.Sort(); // Sorted processing order makes the progress bar more
2355 // meaningful to the user.
2356 } else { // This is a cm93 dataset, specified as yada/yada/cm93
2357 wxString dir_plus = dir_name;
2358 dir_plus += wxFileName::GetPathSeparator();
2359 FileList.Add(dir_plus);
2360 }
2361
2362 int nFile = FileList.GetCount();
2363
2364 if (!nFile) return false;
2365
2366 int nDirEntry = 0;
2367
2368 // Check to see if there are any charts in the DB which refer to this
2369 // directory If none at all, there is no need to scan the DB for fullpath
2370 // match of each potential addition and bthis_dir_in_dB is false.
2371 bool bthis_dir_in_dB = IsChartDirUsed(dir_name);
2372
2373 if (pprog) pprog->SetTitle(_("OpenCPN Chart Add...."));
2374
2375 // build a hash table based on filename (without directory prefix) of
2376 // the chart to fast to detect identical charts
2377 ChartCollisionsHashMap collision_map;
2378 int nEntry = active_chartTable.GetCount();
2379 for (int i = 0; i < nEntry; i++) {
2380 wxString table_file_name = active_chartTable[i].GetFullSystemPath();
2381 wxFileName table_file(table_file_name);
2382 collision_map[table_file.GetFullName()] = i;
2383 }
2384
2385 int nFileProgressQuantum = wxMax(nFile / 100, 2);
2386 double rFileProgressRatio = 100.0 / wxMax(nFile, 1);
2387
2388 for (int ifile = 0; ifile < nFile; ifile++) {
2389 wxFileName file(FileList[ifile]);
2390 wxString full_name = file.GetFullPath();
2391 wxString file_name = file.GetFullName();
2392 wxString utf8_path = full_name;
2393
2394#ifdef __ANDROID__
2395 // The full path (full_name) is the broken Android files system
2396 // interpretation, which does not display well onscreen. So, here we
2397 // reconstruct a full path spec in UTF-8 encoding for later use in string
2398 // displays. This utf-8 string will be used to construct the chart database
2399 // entry if required.
2400 wxFileName fnbase(dir_name_base);
2401 int nDirs = fnbase.GetDirCount();
2402
2403 wxFileName file_target(FileList[ifile]);
2404
2405 for (int i = 0; i < nDirs + 1;
2406 i++) // strip off the erroneous intial directories
2407 file_target.RemoveDir(0);
2408
2409 wxString leftover_path = file_target.GetFullPath();
2410 utf8_path =
2411 dir_name_base + leftover_path; // reconstruct a fully utf-8 version
2412#endif
2413
2414 // Validate the file name again, considering MSW's semi-random treatment
2415 // of case....
2416 // TODO...something fishy here - may need to normalize saved name?
2417 if (!file_name.Matches(lowerFileSpec) && !file_name.Matches(filespec) &&
2418 !file_name.Matches(lowerFileSpecXZ) && !file_name.Matches(filespecXZ) &&
2419 !b_found_cm93) {
2420 // wxLogMessage("FileSpec test failed for:" + file_name);
2421 continue;
2422 }
2423
2424 if (pprog && ((ifile % nFileProgressQuantum) == 0))
2425 pprog->Update(static_cast<int>(ifile * rFileProgressRatio), utf8_path);
2426
2427 ChartTableEntry *pnewChart = NULL;
2428 bool bAddFinal = true;
2429 int b_add_msg = 0;
2430
2431 // Check the collisions map looking for duplicates, and choosing the right
2432 // one.
2433 ChartCollisionsHashMap::const_iterator collision_ptr =
2434 collision_map.find(file_name);
2435 bool collision = (collision_ptr != collision_map.end());
2436 bool file_path_is_same = false;
2437 bool file_time_is_same = false;
2438 ChartTableEntry *pEntry = NULL;
2439 wxString table_file_name;
2440
2441 // Allow multiple cm93 chart sets #4217
2442 if (b_found_cm93) collision = false;
2443
2444 if (collision) {
2445 pEntry = &active_chartTable[collision_ptr->second];
2446 table_file_name = pEntry->GetFullSystemPath();
2447 file_path_is_same =
2448 bthis_dir_in_dB && full_name.IsSameAs(table_file_name);
2449
2450 // If the chart full file paths are exactly the same, select the newer
2451 // one.
2452 if (file_path_is_same) {
2453 b_add_msg++;
2454
2455 // Check the file modification time
2456 time_t t_oldFile = pEntry->GetFileTime();
2457 time_t t_newFile = file.GetModificationTime().GetTicks();
2458
2459 if (t_newFile <= t_oldFile) {
2460 file_time_is_same = true;
2461 bAddFinal = false;
2462 pEntry->SetValid(true);
2463 } else {
2464 bAddFinal = true;
2465 pEntry->SetValid(false);
2466 }
2467 }
2468 }
2469
2470 wxString msg_fn(full_name);
2471 msg_fn.Replace("%", "%%");
2472 if (file_time_is_same) {
2473 // Produce the same output without actually calling
2474 // `CreateChartTableEntry()`.
2475 wxLogMessage(
2476 wxString::Format("Loading chart data for %s", msg_fn.c_str()));
2477 } else {
2478 pnewChart = CreateChartTableEntry(full_name, utf8_path, chart_desc);
2479 if (!pnewChart) {
2480 bAddFinal = false;
2481 wxLogMessage(wxString::Format(
2482 " CreateChartTableEntry() failed for file: %s", msg_fn.c_str()));
2483 }
2484 }
2485
2486 if (!collision || !pnewChart) {
2487 // Do nothing.
2488 } else if (file_path_is_same) {
2489 wxLogMessage(wxString::Format(
2490 " Replacing older chart file of same path: %s", msg_fn.c_str()));
2491 } else if (!file_time_is_same) {
2492 // Look at the chart file name (without directory prefix) for a further
2493 // check for duplicates This catches the case in which the "same" chart
2494 // is in different locations, and one may be newer than the other.
2495 b_add_msg++;
2496
2497 if (pnewChart->IsEarlierThan(*pEntry)) {
2498 wxFileName table_file(table_file_name);
2499 // Make sure the compare file actually exists
2500 if (table_file.IsFileReadable()) {
2501 pEntry->SetValid(true);
2502 bAddFinal = false;
2503 wxLogMessage(
2504 wxString::Format(" Retaining newer chart file of same name: %s",
2505 msg_fn.c_str()));
2506 }
2507 } else if (pnewChart->IsEqualTo(*pEntry)) {
2508 // The file names (without dir prefix) are identical,
2509 // and the mod times are identical
2510 // Prsume that this is intentional, in order to facilitate
2511 // having the same chart in multiple groups.
2512 // So, add this chart.
2513 bAddFinal = true;
2514 } else {
2515 pEntry->SetValid(false);
2516 bAddFinal = true;
2517 wxLogMessage(wxString::Format(
2518 " Replacing older chart file of same name: %s", msg_fn.c_str()));
2519 }
2520 }
2521
2522 if (bAddFinal) {
2523 if (0 == b_add_msg) {
2524 wxLogMessage(
2525 wxString::Format(" Adding chart file: %s", msg_fn.c_str()));
2526 }
2527 collision_map[file_name] = active_chartTable.GetCount();
2528 active_chartTable.Add(pnewChart);
2529 nDirEntry++;
2530 } else {
2531 if (pnewChart) delete pnewChart;
2532 // wxLogMessage(wxString::Format(" Not adding
2533 // chart file: %s", msg_fn.c_str()));
2534 }
2535 }
2536
2537 m_nentries = active_chartTable.GetCount();
2538
2539 return nDirEntry;
2540}
2541#endif
2542
2543int ChartDatabase::SearchDirAndAddCharts(wxString &dir_name_base,
2544 ChartClassDescriptor &chart_desc,
2545 wxGenericProgressDialog *pprog) {
2546 wxString msg("Searching directory: ");
2547 msg += dir_name_base;
2548 msg += " for ";
2549 msg += chart_desc.m_search_mask;
2550 wxLogMessage(msg);
2551
2552 wxString dir_name = dir_name_base;
2553
2554#ifdef __ANDROID__
2555 dir_name = wxString(dir_name_base.mb_str(wxConvUTF8)); // android
2556#endif
2557
2558 if (!wxDir::Exists(dir_name)) return 0;
2559
2560 wxString filespec = chart_desc.m_search_mask.Upper();
2561 wxString lowerFileSpec = chart_desc.m_search_mask.Lower();
2562 wxString filespecXZ = filespec + ".xz";
2563 wxString lowerFileSpecXZ = lowerFileSpec + ".xz";
2564 wxString filename;
2565
2566 // Count the files
2567 wxArrayString FileList;
2568 int gaf_flags = wxDIR_DEFAULT; // as default, recurse into subdirs
2569
2570 // Here is an optimization for MSW/cm93 especially
2571 // If this directory seems to be a cm93, and we are not explicitely looking
2572 // for cm93, then abort Otherwise, we will be looking thru entire cm93 tree
2573 // for non-existent .KAP files, etc.
2574
2575 bool b_found_cm93 = false;
2576 bool b_cm93 = Check_CM93_Structure(dir_name);
2577 if (b_cm93) {
2578 if (filespec != "00300000.A")
2579 return false;
2580 else {
2581 filespec = dir_name;
2582 b_found_cm93 = true;
2583 }
2584 }
2585
2586 if (!b_found_cm93) {
2587 wxDir dir(dir_name);
2588 dir.GetAllFiles(dir_name, &FileList, filespec, gaf_flags);
2589
2590#ifdef __ANDROID__
2591 if (!FileList.GetCount()) {
2592 wxArrayString afl = androidTraverseDir(dir_name, filespec);
2593 for (wxArrayString::const_iterator item = afl.begin(); item != afl.end();
2594 item++)
2595 FileList.Add(*item);
2596 }
2597#endif
2598
2599#ifndef __WXMSW__
2600 if (filespec != lowerFileSpec) {
2601 // add lowercase filespec files too
2602 wxArrayString lowerFileList;
2603 dir.GetAllFiles(dir_name, &lowerFileList, lowerFileSpec, gaf_flags);
2604
2605#ifdef __ANDROID__
2606 if (!lowerFileList.GetCount()) {
2607 wxArrayString afl = androidTraverseDir(dir_name, lowerFileSpec);
2608 for (wxArrayString::const_iterator item = afl.begin();
2609 item != afl.end(); item++)
2610 lowerFileList.Add(*item);
2611 }
2612#endif
2613
2614 for (wxArrayString::const_iterator item = lowerFileList.begin();
2615 item != lowerFileList.end(); item++)
2616 FileList.Add(*item);
2617 }
2618#endif
2619
2620#ifdef OCPN_USE_LZMA
2621 // add xz compressed files;
2622 dir.GetAllFiles(dir_name, &FileList, filespecXZ, gaf_flags);
2623 dir.GetAllFiles(dir_name, &FileList, lowerFileSpecXZ, gaf_flags);
2624#endif
2625
2626 FileList.Sort(); // Sorted processing order makes the progress bar more
2627 // meaningful to the user.
2628 } else { // This is a cm93 dataset, specified as yada/yada/cm93
2629 wxString dir_plus = dir_name;
2630 dir_plus += wxFileName::GetPathSeparator();
2631 FileList.Add(dir_plus);
2632 }
2633
2634 int nFile = FileList.GetCount();
2635
2636 if (!nFile) return false;
2637
2638 int nDirEntry = 0;
2639
2640 // Check to see if there are any charts in the DB which refer to this
2641 // directory If none at all, there is no need to scan the DB for fullpath
2642 // match of each potential addition and bthis_dir_in_dB is false.
2643 // bool bthis_dir_in_dB = IsChartDirUsed(dir_name);
2644
2645 if (pprog) pprog->SetTitle(_("OpenCPN Chart Add...."));
2646
2647 // build a hash table based on filename (without directory prefix) of
2648 // the chart to fast detect identical charts
2649 ChartCollisionsHashMap collision_map;
2650 // int nEntry = active_chartTable.GetCount();
2651 // for (int i = 0; i < nEntry; i++) {
2652 // wxString table_file_name = active_chartTable[i].GetFullSystemPath();
2653 // wxFileName table_file(table_file_name);
2654 // collision_map[table_file.GetFullName()] = i;
2655 // }
2656
2657 // Capture a vector of pending thread tickets for this directory
2658 std::vector<std::shared_ptr<ChartTableEntryJobTicket>> ticket_vector;
2659
2660 for (int ifile = 0; ifile < nFile; ifile++) {
2661 wxFileName file(FileList[ifile]);
2662 wxString full_path = file.GetFullPath();
2663 wxString file_name = file.GetFullName();
2664 wxString utf8_path = full_path;
2665
2666#ifdef __ANDROID__
2667 // The full path (full_name) is the broken Android files system
2668 // interpretation, which does not display well onscreen. So, here we
2669 // reconstruct a full path spec in UTF-8 encoding for later use in string
2670 // displays. This utf-8 string will be used to construct the chart database
2671 // entry if required.
2672 wxFileName fnbase(dir_name_base);
2673 int nDirs = fnbase.GetDirCount();
2674
2675 wxFileName file_target(FileList[ifile]);
2676
2677 for (int i = 0; i < nDirs + 1;
2678 i++) // strip off the erroneous intial directories
2679 file_target.RemoveDir(0);
2680
2681 wxString leftover_path = file_target.GetFullPath();
2682 utf8_path =
2683 dir_name_base + leftover_path; // reconstruct a fully utf-8 version
2684#endif
2685
2686 // Validate the file name again, considering MSW's semi-random treatment
2687 // of case....
2688 // TODO...something fishy here - may need to normalize saved name?
2689 if (!file_name.Matches(lowerFileSpec) && !file_name.Matches(filespec) &&
2690 !file_name.Matches(lowerFileSpecXZ) && !file_name.Matches(filespecXZ) &&
2691 !b_found_cm93) {
2692 // wxLogMessage("FileSpec test failed for:" + file_name);
2693 continue;
2694 }
2695
2696 // if (pprog && ((ifile % nFileProgressQuantum) == 0))
2697 // pprog->Update(static_cast<int>(ifile * rFileProgressRatio), utf8_path);
2698
2699 ChartTableEntry *pnewChart = NULL;
2700 bool bAddFinal = true;
2701 int b_add_msg = 0;
2702
2703#if 0
2704 // Check the collisions map looking for duplicates in directories already
2705 // processed, and choosing the right one.
2706// ChartCollisionsHashMap::const_iterator collision_ptr =
2707// collision_map.find(file_name);
2708// bool collision = (collision_ptr != collision_map.end());
2709// bool file_path_is_same = false;
2710// bool file_time_is_same = false;
2711// ChartTableEntry *pEntry = NULL;
2712// wxString table_file_name;
2713
2714
2715 // Allow multiple cm93 chart sets #4217
2716 if (b_found_cm93) collision = false;
2717
2718 if (collision) {
2719 pEntry = &active_chartTable[collision_ptr->second];
2720 table_file_name = pEntry->GetFullSystemPath();
2721 file_path_is_same =
2722 bthis_dir_in_dB && full_name.IsSameAs(table_file_name);
2723
2724 // If the chart full file paths are exactly the same, select the newer
2725 // one.
2726 if (file_path_is_same) {
2727 b_add_msg++;
2728
2729 // Check the file modification time
2730 time_t t_oldFile = pEntry->GetFileTime();
2731 time_t t_newFile = file.GetModificationTime().GetTicks();
2732
2733 if (t_newFile <= t_oldFile) {
2734 file_time_is_same = true;
2735 bAddFinal = false;
2736 pEntry->SetValid(true);
2737 } else {
2738 bAddFinal = true;
2739 pEntry->SetValid(false);
2740 }
2741 }
2742 }
2743#endif
2744
2745 // Check for duplicates within this directory
2746 bool collision_found = false;
2747 ChartCollisionsHashMap::iterator it;
2748 for (it = collision_map.begin(); it != collision_map.end(); ++it) {
2749 if (it->first.IsSameAs(file_name)) {
2750 // Two files found with identical file name
2751 // For now, just drop this ticket
2752 // TODO Make an (expensive) test on file modification times
2753 collision_found = true;
2754 break;
2755 }
2756 }
2757
2758 if (!collision_found) {
2759 // Create a ticket for this chart
2760 auto ticket = std::make_shared<ChartTableEntryJobTicket>();
2761 ticket->m_ChartPath = full_path;
2762 ticket->m_ChartPathUTF8 = full_path;
2763 ticket->chart_desc = chart_desc;
2764
2765 ticket_vector.push_back(ticket);
2766 collision_map[file_name] = ifile;
2767 }
2768
2769 } // the big loop
2770
2771#if 0 // Cannot happen
2772 bool file_path_is_same = ticket_found->m_ChartPath.IsSameAs(ticket->m_ChartPath);
2773 if (file_path_is_same) {
2774 // Check the file modification time
2775 time_t t_oldFile = pEntry->GetFileTime();
2776 time_t t_newFile = file.GetModificationTime().GetTicks();
2777
2778 if (t_newFile <= t_oldFile) {
2779 file_time_is_same = true;
2780 bAddFinal = false;
2781 pEntry->SetValid(true);
2782 } else {
2783 bAddFinal = true;
2784 pEntry->SetValid(false);
2785 }
2786 }
2787#endif
2788
2789 // Built-in chart types are all thread-safe
2790 // Check plugin charts
2791 if (chart_desc.m_descriptor_type == PLUGIN_DESCRIPTOR) {
2792 // Check plugin charts API. API >- 118 are known to be thread-safe
2793 opencpn_plugin *plugin = chart_desc.m_plugin;
2794 opencpn_plugin_118 *provider_plugin118 = dynamic_cast<opencpn_plugin_118 *>(
2795 dynamic_cast<opencpn_plugin *>(plugin));
2796
2797 if (!provider_plugin118) {
2798 // old plugin, charts not thread-safe
2799 // So arrange to process the tickets in the main app thread,
2800 // during the event handler, at the end of the queue processing
2801 for (auto &ticket : ticket_vector) {
2802 ticket->b_thread_safe = false;
2803 ticket->m_provider_type = 1;
2804 ticket->provider_class_name = chart_desc.m_class_name;
2805 m_deferred_ticket_vector.push_back(ticket);
2806 }
2807 return nDirEntry;
2808 }
2809 }
2810
2811 // Chart directory is known thread-safe
2812 // load the thread pool from the prepared array
2813 // Enqueue jobs
2814 for (auto &ticket : ticket_vector) {
2815 m_pool.Push(ticket);
2816 m_jobsRemaining++;
2817 }
2818
2819 // Tickets queued, wait for completion events
2820 return nDirEntry;
2821}
2822
2823bool ChartDatabase::AddChart(wxString &chartfilename,
2824 ChartClassDescriptor &chart_desc,
2825 wxGenericProgressDialog *pprog, int isearch,
2826 bool bthis_dir_in_dB) {
2827 bool rv = false;
2828 wxFileName file(chartfilename);
2829 wxString full_name = file.GetFullPath();
2830 wxString file_name = file.GetFullName();
2831
2832 // Validate the file name again, considering MSW's semi-random treatment of
2833 // case....
2834 // TODO...something fishy here - may need to normalize saved name?
2835 // if(!file_name.Matches(lowerFileSpec) && !file_name.Matches(filespec) &&
2836 // !b_found_cm93)
2837 // continue;
2838
2839 if (pprog)
2840 pprog->Update(wxMin((m_pdifile * 100) / m_pdnFile, 100), full_name);
2841
2842 ChartTableEntry *pnewChart = NULL;
2843 bool bAddFinal = true;
2844 int b_add_msg = 0;
2845 wxString msg_fn(full_name);
2846 msg_fn.Replace("%", "%%");
2847
2848 pnewChart = CreateChartTableEntry(full_name, full_name, chart_desc);
2849 if (!pnewChart) {
2850 bAddFinal = false;
2851 wxLogMessage(wxString::Format(
2852 " CreateChartTableEntry() failed for file: %s", msg_fn.c_str()));
2853 return false;
2854 } else // traverse the existing database looking for duplicates, and choosing
2855 // the right one
2856 {
2857 int nEntry = active_chartTable.size();
2858 for (int i = 0; i < nEntry; i++) {
2859 auto &cte_a = GetChartTableEntry(i);
2860 wxString *ptable_file_name = cte_a.GetpsFullPath();
2861
2862 // If the chart full file paths are exactly the same, select the newer
2863 // one
2864 if (bthis_dir_in_dB && full_name.IsSameAs(*ptable_file_name)) {
2865 b_add_msg++;
2866
2867 // Check the file modification time
2868 auto &cte_search = GetChartTableEntry(isearch);
2869 time_t t_oldFile = cte_search.GetFileTime();
2870 time_t t_newFile = file.GetModificationTime().GetTicks();
2871
2872 if (t_newFile <= t_oldFile) {
2873 bAddFinal = false;
2874 cte_search.SetValid(true);
2875 } else {
2876 bAddFinal = true;
2877 cte_search.SetValid(false);
2878 wxLogMessage(
2879 wxString::Format(" Replacing older chart file of same path: %s",
2880 msg_fn.c_str()));
2881 }
2882
2883 break;
2884 }
2885
2886 // Look at the chart file name (without directory prefix) for a further
2887 // check for duplicates This catches the case in which the "same" chart
2888 // is in different locations, and one may be newer than the other.
2889 wxFileName table_file(*ptable_file_name);
2890
2891 if (table_file.GetFullName() == file_name) {
2892 b_add_msg++;
2893
2894 // TODO fix this...
2895#if 0
2896 if (pnewChart->IsEarlierThan(active_chartTable[isearch])) {
2897 // Make sure the compare file actually exists
2898 if (table_file.IsFileReadable()) {
2899 active_chartTable[isearch].SetValid(true);
2900 bAddFinal = false;
2901 wxLogMessage(wxString::Format(
2902 " Retaining newer chart file of same name: %s",
2903 msg_fn.c_str()));
2904 }
2905 } else if (pnewChart->IsEqualTo(active_chartTable[isearch])) {
2906 // The file names (without dir prefix) are identical,
2907 // and the mod times are identical
2908 // Prsume that this is intentional, in order to facilitate
2909 // having the same chart in multiple groups.
2910 // So, add this chart.
2911 bAddFinal = true;
2912 }
2913
2914 else {
2915 active_chartTable[isearch].SetValid(false);
2916 bAddFinal = true;
2917 wxLogMessage(
2918 wxString::Format(" Replacing older chart file of same name: %s",
2919 msg_fn.c_str()));
2920 }
2921#endif
2922 break;
2923 }
2924
2925 // TODO Look at the chart ID as a further check against duplicates
2926
2927 isearch++;
2928 if (nEntry == isearch) isearch = 0;
2929 } // for
2930 }
2931
2932 if (bAddFinal) {
2933 if (0 == b_add_msg) {
2934 wxLogMessage(
2935 wxString::Format(" Adding chart file: %s", msg_fn.c_str()));
2936 }
2937
2938 // TODO Fix
2939 // active_chartTable.push_back(pnewChart);
2940
2941 rv = true;
2942 } else {
2943 delete pnewChart;
2944 // wxLogMessage(wxString::Format(" Not adding chart
2945 // file: %s", msg_fn.c_str()));
2946 rv = false;
2947 }
2948
2949 m_nentries = active_chartTable.size();
2950
2951 return rv;
2952}
2953
2954bool ChartDatabase::AddSingleChart(wxString &ChartFullPath,
2955 bool b_force_full_search) {
2956 // Find a relevant chart class descriptor
2957 wxFileName fn(ChartFullPath);
2958 wxString ext = fn.GetExt();
2959 ext.Prepend("*.");
2960 wxString ext_upper = ext.MakeUpper();
2961 wxString ext_lower = ext.MakeLower();
2962 wxString dir_name = fn.GetPath();
2963
2964 // Search the array of chart class descriptors to find a match
2965 // bewteen the search mask and the the chart file extension
2966
2968 for (auto &cd : m_ChartClassDescriptorArray) {
2969 if (cd.m_descriptor_type == PLUGIN_DESCRIPTOR) {
2970 if (cd.m_search_mask == ext_upper) {
2971 desc = cd;
2972 break;
2973 }
2974 if (cd.m_search_mask == ext_lower) {
2975 desc = cd;
2976 break;
2977 }
2978 }
2979 }
2980
2981 // If we know that we need to do a full recursive search of the db,
2982 // then there is no need to verify it by doing a directory match
2983 bool b_recurse = true;
2984 if (!b_force_full_search) b_recurse = IsChartDirUsed(dir_name);
2985
2986 bool rv = AddChart(ChartFullPath, desc, NULL, 0, b_recurse);
2987
2988 // remove duplicates marked in AddChart()
2989
2990 for (unsigned int i = 0; i < active_chartTable.size(); i++) {
2991 auto &cte_r = GetChartTableEntry(i);
2992 if (!cte_r.GetbValid()) {
2993 // TODO fix this
2994 // active_chartTable.RemoveAt(i);
2995 i--; // entry is gone, recheck this index for next entry
2996 }
2997 }
2998
2999 // Update the Entry index fields
3000 for (unsigned int i = 0; i < active_chartTable.size(); i++) {
3001 auto &cte_ef = GetChartTableEntry(i);
3002 cte_ef.SetEntryOffset(i);
3003 }
3004
3005 // Get a new magic number
3006 wxString new_magic;
3007 DetectDirChange(dir_name, "", "", new_magic, 0);
3008
3009 // Update (clone) the CDI array
3010 bool bcfound = false;
3011 ArrayOfCDI NewChartDirArray;
3012
3013 ArrayOfCDI ChartDirArray = GetChartDirArray();
3014 for (unsigned int i = 0; i < ChartDirArray.GetCount(); i++) {
3015 ChartDirInfo cdi = ChartDirArray[i];
3016
3017 ChartDirInfo newcdi = cdi;
3018
3019 // If entry is found that matches this cell, clear the magic number.
3020 if (newcdi.fullpath == dir_name) {
3021 newcdi.magic_number = new_magic;
3022 bcfound = true;
3023 }
3024
3025 NewChartDirArray.Add(newcdi);
3026 }
3027
3028 if (!bcfound) {
3029 ChartDirInfo cdi;
3030 cdi.fullpath = dir_name;
3031 cdi.magic_number = new_magic;
3032 NewChartDirArray.Add(cdi);
3033 }
3034
3035 // Update the database master copy of the CDI array
3036 SetChartDirArray(NewChartDirArray);
3037
3038 // Update the list of chart dirs.
3039 m_chartDirs.Clear();
3040
3041 for (unsigned int i = 0; i < GetChartDirArray().GetCount(); i++) {
3042 ChartDirInfo cdi = GetChartDirArray()[i];
3043 m_chartDirs.Add(cdi.fullpath);
3044 }
3045
3046 m_nentries = active_chartTable.size();
3047
3048 return rv;
3049}
3050
3051bool ChartDatabase::RemoveSingleChart(wxString &ChartFullPath) {
3052 // TODO fix this
3053 return false;
3054#if 0
3055 bool rv = false;
3056
3057 // Walk the chart table, looking for the target
3058 for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
3059 if (ChartFullPath.IsSameAs(GetChartTableEntry(i).GetFullSystemPath())) {
3060 active_chartTable.RemoveAt(i);
3061 break;
3062 }
3063 }
3064
3065 // Update the EntryOffset fields for the array
3066 ChartTableEntry *pcte;
3067
3068 for (unsigned int i = 0; i < active_chartTable.GetCount(); i++) {
3069 pcte = GetpChartTableEntry(i);
3070 pcte->SetEntryOffset(i);
3071 }
3072
3073 // Check and update the dir array
3074 wxFileName fn(ChartFullPath);
3075 wxString fd = fn.GetPath();
3076 if (!IsChartDirUsed(fd)) {
3077 // Clone a new array, removing the unused directory,
3078 ArrayOfCDI NewChartDirArray;
3079
3080 ArrayOfCDI ChartDirArray = GetChartDirArray();
3081 for (unsigned int i = 0; i < ChartDirArray.GetCount(); i++) {
3082 ChartDirInfo cdi = ChartDirArray[i];
3083
3084 ChartDirInfo newcdi = cdi;
3085
3086 if (newcdi.fullpath != fd) NewChartDirArray.Add(newcdi);
3087 }
3088
3089 SetChartDirArray(NewChartDirArray);
3090 }
3091
3092 // Update the list of chart dirs.
3093 m_chartDirs.Clear();
3094 for (unsigned int i = 0; i < GetChartDirArray().GetCount(); i++) {
3095 ChartDirInfo cdi = GetChartDirArray()[i];
3096 m_chartDirs.Add(cdi.fullpath);
3097 }
3098
3099 m_nentries = active_chartTable.GetCount();
3100
3101 return rv;
3102#endif
3103}
3104
3106// Create a Chart object
3108
3109ChartBase *ChartDatabase::GetChart(const wxChar *theFilePath,
3110 ChartClassDescriptor &chart_desc) const {
3111 // TODO: support non-UI chart factory
3112 return NULL;
3113}
3114
3116// Create Chart Table entry by reading chart header info, etc.
3118
3119ChartTableEntry *ChartDatabase::CreateChartTableEntry(
3120 const wxString &filePath, wxString &utf8Path,
3121 ChartClassDescriptor &chart_desc) {
3122 // printf("CreateCTE\n");
3123 wxString msg_fn(filePath);
3124 msg_fn.Replace("%", "%%");
3125 wxLogMessage(wxString::Format("Loading chart data for %s", msg_fn.c_str()));
3126
3127 ChartBase *pch = GetChart(filePath, chart_desc);
3128 if (pch == NULL) {
3129 wxLogMessage(
3130 wxString::Format(" ...creation failed for %s", msg_fn.c_str()));
3131 return NULL;
3132 }
3133
3134 InitReturn rc = pch->Init(filePath, HEADER_ONLY);
3135 // printf("Back from Init\n");
3136 if (rc != INIT_OK) {
3137 // printf(" ###ERROR\n");
3138 delete pch;
3139 wxLogMessage(
3140 wxString::Format(" ...initialization failed for %s", msg_fn.c_str()));
3141 return NULL;
3142 }
3143
3144 ChartTableEntry *ret_val = new ChartTableEntry(*pch, utf8Path);
3145 ret_val->SetValid(true);
3146
3147 delete pch;
3148
3149 return ret_val;
3150}
3151
3152bool ChartDatabase::GetCentroidOfLargestScaleChart(double *clat, double *clon,
3153 ChartFamilyEnum family) {
3154 int cur_max_i = -1;
3155 int cur_max_scale = 0;
3156
3157 int nEntry = active_chartTable.size();
3158
3159 for (int i = 0; i < nEntry; i++) {
3160 auto &cte_fam = GetChartTableEntry(i);
3161 if (GetChartFamily(cte_fam.GetChartType()) == family) {
3162 if (cte_fam.GetScale() > cur_max_scale) {
3163 cur_max_scale = cte_fam.GetScale();
3164 cur_max_i = i;
3165 }
3166 }
3167 }
3168
3169 if (cur_max_i == -1)
3170 return false; // nothing found
3171 else {
3172 auto &cte_sel = GetChartTableEntry(cur_max_i);
3173 *clat = (cte_sel.GetLatMax() + cte_sel.GetLatMin()) / 2.;
3174 *clon = (cte_sel.GetLonMin() + cte_sel.GetLonMax()) / 2.;
3175 }
3176 return true;
3177}
3178
3179//-------------------------------------------------------------------
3180// Get DBChart Projection
3181//-------------------------------------------------------------------
3182int ChartDatabase::GetDBChartProj(int dbIndex) {
3183 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3184 auto &cte = GetChartTableEntry(dbIndex);
3185 return cte.GetChartProjectionType();
3186 } else
3187 return PROJECTION_UNKNOWN;
3188}
3189
3190//-------------------------------------------------------------------
3191// Get DBChart Family
3192//-------------------------------------------------------------------
3193int ChartDatabase::GetDBChartFamily(int dbIndex) {
3194 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3195 auto &cte = GetChartTableEntry(dbIndex);
3196 return cte.GetChartFamily();
3197 } else
3198 return CHART_FAMILY_UNKNOWN;
3199}
3200
3201//-------------------------------------------------------------------
3202// Get DBChart FullFileName
3203//-------------------------------------------------------------------
3204wxString ChartDatabase::GetDBChartFileName(int dbIndex) {
3205 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3206 auto &cte = GetChartTableEntry(dbIndex);
3207 return wxString(cte.GetFullSystemPath());
3208 } else
3209 return "";
3210}
3211
3212//-------------------------------------------------------------------
3213// Get DBChart Type
3214//-------------------------------------------------------------------
3215int ChartDatabase::GetDBChartType(int dbIndex) {
3216 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3217 auto &cte = GetChartTableEntry(dbIndex);
3218 return cte.GetChartType();
3219 } else
3220 return 0;
3221}
3222
3223//-------------------------------------------------------------------
3224// Get DBChart Skew
3225//-------------------------------------------------------------------
3226float ChartDatabase::GetDBChartSkew(int dbIndex) {
3227 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3228 auto &cte = GetChartTableEntry(dbIndex);
3229 return cte.GetChartSkew();
3230 } else
3231 return 0.;
3232}
3233
3234//-------------------------------------------------------------------
3235// Get DBChart Scale
3236//-------------------------------------------------------------------
3237int ChartDatabase::GetDBChartScale(int dbIndex) {
3238 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3239 auto &cte = GetChartTableEntry(dbIndex);
3240 return cte.GetScale();
3241 } else
3242 return 1;
3243}
3244
3245//-------------------------------------------------------------------
3246// Get Lat/Lon Bounding Box from db
3247//-------------------------------------------------------------------
3248bool ChartDatabase::GetDBBoundingBox(int dbIndex, LLBBox &box) {
3249 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3250 auto &entry = GetChartTableEntry(dbIndex);
3251 box.Set(entry.GetLatMin(), entry.GetLonMin(), entry.GetLatMax(),
3252 entry.GetLonMax());
3253 }
3254
3255 return true;
3256}
3257
3258const LLBBox &ChartDatabase::GetDBBoundingBox(int dbIndex) {
3259 if ((bValid) && (dbIndex >= 0)) {
3260 auto &entry = GetChartTableEntry(dbIndex);
3261 return entry.GetBBox();
3262 } else {
3263 return m_dummy_bbox;
3264 }
3265}
3266
3267//-------------------------------------------------------------------
3268// Get PlyPoint from Database
3269//-------------------------------------------------------------------
3270int ChartDatabase::GetDBPlyPoint(int dbIndex, int plyindex, float *lat,
3271 float *lon) {
3272 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3273 const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
3274 if (entry.GetnPlyEntries()) {
3275 float *fp = entry.GetpPlyTable();
3276 fp += plyindex * 2;
3277 if (lat) *lat = *fp;
3278 fp++;
3279 if (lon) *lon = *fp;
3280 }
3281 return entry.GetnPlyEntries();
3282 } else
3283 return 0;
3284}
3285
3286//-------------------------------------------------------------------
3287// Get AuxPlyPoint from Database
3288//-------------------------------------------------------------------
3289int ChartDatabase::GetDBAuxPlyPoint(int dbIndex, int plyindex, int ply,
3290 float *lat, float *lon) {
3291 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3292 const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
3293 if (entry.GetnAuxPlyEntries()) {
3294 float *fp = entry.GetpAuxPlyTableEntry(ply);
3295
3296 fp += plyindex * 2;
3297 if (lat) *lat = *fp;
3298 fp++;
3299 if (lon) *lon = *fp;
3300 }
3301
3302 return entry.GetAuxCntTableEntry(ply);
3303 } else
3304 return 0;
3305}
3306
3307int ChartDatabase::GetnAuxPlyEntries(int dbIndex) {
3308 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3309 const ChartTableEntry &entry = GetChartTableEntry(dbIndex);
3310 return entry.GetnAuxPlyEntries();
3311 } else
3312 return 0;
3313}
3314
3315//-------------------------------------------------------------------
3316// Get vector of reduced Plypoints
3317//-------------------------------------------------------------------
3318std::vector<float> ChartDatabase::GetReducedPlyPoints(int dbIndex) {
3319 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3320 ChartTableEntry &cte = GetChartTableEntry(dbIndex);
3321 return cte.GetReducedPlyPoints();
3322 }
3323
3324 std::vector<float> dummy;
3325 return dummy;
3326}
3327
3328//-------------------------------------------------------------------
3329// Get vector of reduced AuxPlypoints
3330//-------------------------------------------------------------------
3331std::vector<float> ChartDatabase::GetReducedAuxPlyPoints(int dbIndex,
3332 int iTable) {
3333 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3334 auto &cte = GetChartTableEntry(dbIndex);
3335 return cte.GetReducedAuxPlyPoints(iTable);
3336 }
3337
3338 std::vector<float> dummy;
3339 return dummy;
3340}
3341
3342bool ChartDatabase::IsChartAvailable(int dbIndex) {
3343 if ((bValid) && (dbIndex >= 0) && (dbIndex < (int)active_chartTable.size())) {
3344 auto &cte = GetChartTableEntry(dbIndex);
3345
3346 // If not PLugIn chart, assume always available
3347 if (cte.GetChartType() != CHART_TYPE_PLUGIN) return true;
3348
3349 wxString *path = cte.GetpsFullPath();
3350 wxFileName fn(*path);
3351 wxString ext = fn.GetExt();
3352 ext.Prepend("*.");
3353 wxString ext_upper = ext.MakeUpper();
3354 wxString ext_lower = ext.MakeLower();
3355
3356 // Search the array of chart class descriptors to find a match
3357 // between the search mask and the the chart file extension
3358
3359 for (auto &cd : m_ChartClassDescriptorArray) {
3360 if (cd.m_descriptor_type == PLUGIN_DESCRIPTOR) {
3361 wxString search_mask = cd.m_search_mask;
3362
3363 if (search_mask == ext_upper) {
3364 return true;
3365 }
3366 if (search_mask == ext_lower) {
3367 return true;
3368 }
3369 if (path->Matches(search_mask)) {
3370 return true;
3371 }
3372 }
3373 }
3374 }
3375
3376 return false;
3377}
3378
3379void ChartDatabase::ApplyGroupArray(ChartGroupArray *pGroupArray) {
3380 wxString separator(wxFileName::GetPathSeparator());
3381
3382 for (unsigned int ic = 0; ic < active_chartTable.size(); ic++) {
3383 auto &cte = GetChartTableEntry(ic);
3384
3385 cte.ClearGroupArray();
3386
3387 wxString *chart_full_path = cte.GetpsFullPath();
3388
3389 for (unsigned int igroup = 0; igroup < pGroupArray->GetCount(); igroup++) {
3390 ChartGroup *pGroup = pGroupArray->Item(igroup);
3391 for (const auto &elem : pGroup->m_element_array) {
3392 wxString element_root = elem.m_element_name;
3393
3394 // The element may be a full single chart name
3395 // If so, add it
3396 // Otherwise, append a sep character so that similar paths are
3397 // distinguished. See FS#1060
3398 if (!chart_full_path->IsSameAs(element_root))
3399 element_root.Append(
3400 separator); // Prevent comingling similar looking path names
3401 if (chart_full_path->StartsWith(element_root)) {
3402 bool b_add = true;
3403 for (unsigned int k = 0; k < elem.m_missing_name_array.size(); k++) {
3404 const wxString &missing_item = elem.m_missing_name_array[k];
3405 if (chart_full_path->StartsWith(missing_item)) {
3406 if (chart_full_path->IsSameAs(
3407 missing_item)) // missing item is full chart name
3408 {
3409 b_add = false;
3410 break;
3411 } else {
3412 if (wxDir::Exists(missing_item)) // missing item is a dir
3413 {
3414 b_add = false;
3415 break;
3416 }
3417 }
3418 }
3419 }
3420
3421 if (b_add) cte.AddIntToGroupArray(igroup + 1);
3422 }
3423 }
3424 }
3425 }
3426}
General chart base definitions.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Define threaded chart database classes.
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:60
Basic chart info storage.
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:60
Base class for all chart types.
Definition chartbase.h:126
Manages a database of charts, including reading, writing, and querying chart information.
Definition chartdbs.h:304
bool Create(ArrayOfCDI &dir_array, wxGenericProgressDialog *pprog)
Creates a new chart database from a list of directories.
bool Update(ArrayOfCDI &dir_array, bool bForce, wxGenericProgressDialog *pprog)
Updates the chart database.
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:483
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
Class for computing hash of arbitrary length.
Definition flex_hash.h:34
EventVar options_on_finalize_chartdbs
Notified when chartdbs async operations complete, to finalize settings.
Definition gui_events.h:74
EventVar on_finalize_chartdbs
Notified when chartdbs async operations complete, to reload charts.
Definition gui_events.h:71
Base class for OpenCPN plugins.
Hash of arbitrary length.
Platform independent GL includes.
Misc GUI event vars, a singleton.
wxFont * GetOCPNScaledFont(wxString item, int default_size)
Retrieves a font from FontMgr, optionally scaled for physical readability.
Definition gui_lib.cpp:61
Utility functions.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:187