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