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