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