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