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