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