OpenCPN Partial API docs
Loading...
Searching...
No Matches
Quilt.cpp
1/******************************************************************************
2 *
3 * Project: OpenCPN
4 *
5 ***************************************************************************
6 * Copyright (C) 2013 by David S. Register *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22 ***************************************************************************
23 */
24
25#include <wx/wxprec.h>
26
27#include "config.h"
28#include "Quilt.h"
29#include "chartdb.h"
30#include "s52plib.h"
31#include "chcanv.h"
32#include "ocpn_pixel.h" // for ocpnUSE_DIBSECTION
33#include "model/ocpn_utils.h"
34#include "chartimg.h"
35#ifdef __OCPN__ANDROID__
36#include "androidUTIL.h"
37#endif
38#include <algorithm>
39
40#include "s57chart.h"
41
42#include <wx/listimpl.cpp>
43WX_DEFINE_LIST(PatchList);
44
45extern ChartDB *ChartData;
46extern s52plib *ps52plib;
47extern ColorScheme global_color_scheme;
48extern int g_chart_zoom_modifier_raster;
49extern int g_chart_zoom_modifier_vector;
50extern bool g_fog_overzoom;
51extern double g_overzoom_emphasis_base;
52extern bool g_bopengl;
53
54// We define and use this one Macro in this module
55// Reason: some compilers refuse to inline "GetChartTableEntry()"
56// and so this leads to a push/call sequence for this heavily utilized but
57// short function Note also that in the macor expansion there is no bounds
58// checking on the parameter (i), So it is probably better to confine the
59// macro use to one module, and scrub carefully. Anyway, makes a
60// significant difference with Windows MSVC compiler builds.
61
62#ifndef __OCPN__ANDROID__
63#define GetChartTableEntry(i) GetChartTable()[i]
64#endif
65
66// Calculating the chart coverage region with extremely complicated shape is
67// very expensive, put a limit on the complefity of "not covered" areas to
68// prevent the application from slowing down to total unusability. On US ENC
69// charts, the number of NOCOVR PLYs seems to always be under 300, but on the
70// SCS ENCs it can get as high as 10000 and make the application totally
71// unusable with chart quilting enabled while bringing little real effect.
72#define NOCOVR_PLY_PERF_LIMIT 500
73#define AUX_PLY_PERF_LIMIT 500
74
75static int CompareScales(int i1, int i2) {
76 if (!ChartData) return 0;
77
78 const ChartTableEntry &cte1 = ChartData->GetChartTableEntry(i1);
79 const ChartTableEntry &cte2 = ChartData->GetChartTableEntry(i2);
80
81 if (cte1.Scale_eq(cte2.GetScale())) { // same scales, so sort on dbIndex
82 float lat1, lat2;
83 lat1 = cte1.GetLatMax();
84 lat2 = cte2.GetLatMax();
85 if (roundf(lat1 * 100.) == roundf(lat2 * 100.)) {
86 float lon1, lon2;
87 lon1 = cte1.GetLonMin();
88 lon2 = cte2.GetLonMin();
89 if (lon1 == lon2) {
90 return i1 - i2;
91 } else
92 return (lon1 < lon2) ? -1 : 1;
93 } else
94 return (lat1 < lat2) ? 1 : -1;
95 } else
96 return cte1.GetScale() - cte2.GetScale();
97}
98static bool CompareScalesStd(int i1, int i2) {
99 return CompareScales(i1, i2) < 0;
100}
101
102static int CompareQuiltCandidateScales(QuiltCandidate *qc1,
103 QuiltCandidate *qc2) {
104 if (!ChartData) return 0;
105 return CompareScales(qc1->dbIndex, qc2->dbIndex);
106}
107
108const LLRegion &QuiltCandidate::GetCandidateRegion() {
109 const ChartTableEntry &cte = ChartData->GetChartTableEntry(dbIndex);
110 LLRegion &candidate_region =
111 const_cast<LLRegion &>(cte.quilt_candidate_region);
112
113 if (!candidate_region.Empty()) return candidate_region;
114
115 LLRegion world_region(-90, -180, 90, 180);
116
117 // for cm93 charts use their valid canvas region (should this apply to all
118 // vector charts?)
119 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93COMP) {
120 double cm93_ll_bounds[8] = {-80, -180, -80, 180, 80, 180, 80, -180};
121 candidate_region = LLRegion(4, cm93_ll_bounds);
122 return candidate_region;
123 }
124
125 // If the chart has an aux ply table, use it for finer region precision
126 int nAuxPlyEntries = cte.GetnAuxPlyEntries();
127 if (nAuxPlyEntries >= 1) {
128 candidate_region.Clear();
129 for (int ip = 0; ip < nAuxPlyEntries; ip++) {
130 float *pfp = cte.GetpAuxPlyTableEntry(ip);
131 int nAuxPly = cte.GetAuxCntTableEntry(ip);
132
133 candidate_region.Union(LLRegion(nAuxPly, pfp));
134 }
135 } else {
136 // int n_ply_entries = cte.GetnPlyEntries();
137 // float *pfp = cte.GetpPlyTable();
138 //
139 //
140 // if( n_ply_entries >= 3 ) // could happen with old database and
141 // some charts, e.g. SHOM 2381.kap
142 // candidate_region = LLRegion( n_ply_entries, pfp );
143 // else
144 // candidate_region = world_region;
145
146 std::vector<float> vec = ChartData->GetReducedPlyPoints(dbIndex);
147
148 std::vector<float> vecr;
149 for (size_t i = 0; i < vec.size() / 2; i++) {
150 float a = vec[i * 2 + 1];
151 vecr.push_back(a);
152 a = vec[i * 2];
153 vecr.push_back(a);
154 }
155
156 std::vector<float>::iterator it = vecr.begin();
157
158 if (vecr.size() / 2 >= 3) { // could happen with old database and some
159 // charts, e.g. SHOM 2381.kap
160
161 candidate_region = LLRegion(vecr.size() / 2, (float *)&(*it));
162 } else
163 candidate_region = world_region;
164 }
165
166 // Remove the NoCovr regions
167 if (!candidate_region
168 .Empty()) { // don't bother if the region is already empty
169 int nNoCovrPlyEntries = cte.GetnNoCovrPlyEntries();
170 if (nNoCovrPlyEntries) {
171 for (int ip = 0; ip < nNoCovrPlyEntries; ip++) {
172 float *pfp = cte.GetpNoCovrPlyTableEntry(ip);
173 int nNoCovrPly = cte.GetNoCovrCntTableEntry(ip);
174
175 LLRegion t_region = LLRegion(nNoCovrPly, pfp);
176
177 // For performance reasons, we do not support
178 // no-cover regions with "holes" in them.
179 // This leads to the possibility that a cover region may be fully
180 // embedded within a larger no-cover region.
181 // Handle this case by assuming that such cells will contain more than
182 // one cover region. To create a valid no-cover region we may simply
183 // subtract each cover region from the larger no-cover region.
184 if (nAuxPlyEntries > 1) {
185 for (int ipr = 0; ipr < nAuxPlyEntries; ipr++) {
186 float *pfpr = cte.GetpAuxPlyTableEntry(ipr);
187 int nAuxPly = cte.GetAuxCntTableEntry(ipr);
188 t_region.Subtract(LLRegion(nAuxPly, pfpr));
189 }
190 }
191
192 // We do a test removal of the NoCovr region.
193 // If the result iz empty, it must be that the NoCovr region is
194 // the full extent M_COVR(CATCOV=2) feature found in NOAA ENCs.
195 // We ignore it.
196
197 if (!t_region.Empty()) {
198 LLRegion test_region = candidate_region;
199 test_region.Subtract(t_region);
200 if (!test_region.Empty()) candidate_region = test_region;
201 }
202 }
203 }
204 }
205
206 // Another superbad hack....
207 // Super small scale raster charts like bluemarble.kap usually cross the
208 // prime meridian and Plypoints georef is problematic...... So, force full
209 // screen coverage in the quilt
210 if ((cte.GetScale() > 90000000) &&
211 (cte.GetChartFamily() == CHART_FAMILY_RASTER))
212 candidate_region = world_region;
213
214 return candidate_region;
215}
216
217LLRegion &QuiltCandidate::GetReducedCandidateRegion(double factor) {
218 if (factor != last_factor) {
219 reduced_candidate_region = GetCandidateRegion();
220 reduced_candidate_region.Reduce(factor);
221 last_factor = factor;
222 }
223
224 return reduced_candidate_region;
225}
226
227void QuiltCandidate::SetScale(int scale) {
228 ChartScale = scale;
229 rounding = 0;
230 // XXX find the right rounding
231 if (scale >= 1000) rounding = 5 * pow(10, log10(scale) - 2);
232}
233
234Quilt::Quilt(ChartCanvas *parent) {
235 // m_bEnableRaster = true;
236 // m_bEnableVector = false;;
237 // m_bEnableCM93 = false;
238
239 m_parent = parent;
240 m_reference_scale = 1;
241 m_refchart_dbIndex = -1;
242 m_reference_type = CHART_TYPE_UNKNOWN;
243 m_reference_family = CHART_FAMILY_UNKNOWN;
244 m_quilt_proj = PROJECTION_UNKNOWN;
245
246 m_lost_refchart_dbIndex = -1;
247
248 cnode = NULL;
249
250 m_pBM = NULL;
251 m_bcomposed = false;
252 m_bbusy = false;
253 m_b_hidef = false;
254
255 m_pcandidate_array =
256 new ArrayOfSortedQuiltCandidates(CompareQuiltCandidateScales);
257 m_nHiLiteIndex = -1;
258
259 m_zout_family = -1;
260 m_zout_type = -1;
261 m_preferred_family = CHART_FAMILY_RASTER;
262
263 // Quilting of skewed raster charts is allowed for OpenGL only
264 m_bquiltskew = g_bopengl;
265 // Quilting of different projections is allowed for OpenGL only
266 m_bquiltanyproj = g_bopengl;
267}
268
269Quilt::~Quilt() {
270 m_PatchList.DeleteContents(true);
271 m_PatchList.Clear();
272
273 EmptyCandidateArray();
274 delete m_pcandidate_array;
275
276 m_extended_stack_array.clear();
277
278 delete m_pBM;
279}
280
281bool Quilt::IsVPBlittable(ViewPort &VPoint, int dx, int dy,
282 bool b_allow_vector) {
283 if (!m_vp_rendered.IsValid()) return false;
284
285 wxPoint2DDouble p1 =
286 VPoint.GetDoublePixFromLL(m_vp_rendered.clat, m_vp_rendered.clon);
287 wxPoint2DDouble p2 = VPoint.GetDoublePixFromLL(VPoint.clat, VPoint.clon);
288 double deltax = p2.m_x - p1.m_x;
289 double deltay = p2.m_y - p1.m_y;
290
291 if ((fabs(deltax - dx) > 1e-2) || (fabs(deltay - dy) > 1e-2)) return false;
292
293 return true;
294}
295
296bool Quilt::IsChartS57Overlay(int db_index) {
297 if (db_index < 0) return false;
298
299 const ChartTableEntry &cte = ChartData->GetChartTableEntry(db_index);
300 if (CHART_FAMILY_VECTOR == cte.GetChartFamily()) {
301 return s57chart::IsCellOverlayType(cte.GetFullSystemPath());
302 } else
303 return false;
304}
305
306bool Quilt::IsChartQuiltableRef(int db_index) {
307 if (db_index < 0 || db_index > ChartData->GetChartTableEntries() - 1)
308 return false;
309
310 // Is the chart targeted by db_index useable as a quilt reference chart?
311 const ChartTableEntry &ctei = ChartData->GetChartTableEntry(db_index);
312
313 bool bproj_match = true; // Accept all projections
314
315 double skew_norm = ctei.GetChartSkew();
316 if (skew_norm > 180.) skew_norm -= 360.;
317
318 bool skew_match =
319 fabs(skew_norm) < 1.; // Only un-skewed charts are acceptable for quilt
320 if (m_bquiltskew) skew_match = true;
321
322 // In noshow array?
323 bool b_noshow = false;
324 for (unsigned int i = 0; i < m_parent->GetQuiltNoshowIindexArray().size();
325 i++) {
326 if (m_parent->GetQuiltNoshowIindexArray()[i] ==
327 db_index) // chart is in the noshow list
328 {
329 b_noshow = true;
330 break;
331 }
332 }
333
334 return (bproj_match & skew_match & !b_noshow);
335}
336
337bool Quilt::IsChartInQuilt(ChartBase *pc) {
338 // Iterate thru the quilt
339 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
340 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
341 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
342 if (ChartData->OpenChartFromDB(pqc->dbIndex, FULL_INIT) == pc)
343 return true;
344 }
345 }
346 return false;
347}
348
349bool Quilt::IsChartInQuilt(wxString &full_path) {
350 // Iterate thru the quilt
351 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
352 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
353 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
354 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(pqc->dbIndex);
355 if (pcte->GetpsFullPath()->IsSameAs(full_path)) return true;
356 }
357 }
358 return false;
359}
360
361std::vector<int> Quilt::GetCandidatedbIndexArray(bool from_ref_chart,
362 bool exclude_user_hidden) {
363 std::vector<int> ret;
364 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
365 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
366 if (from_ref_chart) // only add entries of smaller scale than ref scale
367 {
368 if (pqc->Scale_ge(m_reference_scale)) {
369 // Search the no-show array
370 if (exclude_user_hidden) {
371 bool b_noshow = false;
372 for (unsigned int i = 0;
373 i < m_parent->GetQuiltNoshowIindexArray().size(); i++) {
374 if (m_parent->GetQuiltNoshowIindexArray()[i] ==
375 pqc->dbIndex) // chart is in the noshow list
376 {
377 b_noshow = true;
378 break;
379 }
380 }
381 if (!b_noshow) ret.push_back(pqc->dbIndex);
382 } else {
383 ret.push_back(pqc->dbIndex);
384 }
385 }
386 } else
387 ret.push_back(pqc->dbIndex);
388 }
389 return ret;
390}
391
392QuiltPatch *Quilt::GetCurrentPatch() {
393 if (cnode)
394 return (cnode->GetData());
395 else
396 return NULL;
397}
398
399void Quilt::EmptyCandidateArray(void) {
400 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
401 delete m_pcandidate_array->Item(i);
402 }
403
404 m_pcandidate_array->Clear();
405}
406
407ChartBase *Quilt::GetFirstChart() {
408 if (!ChartData) return NULL;
409
410 if (!ChartData->IsValid()) // This could happen during yield recursion from
411 // progress dialog during databse update
412 return NULL;
413
414 if (!m_bcomposed) return NULL;
415
416 if (m_bbusy) return NULL;
417
418 m_bbusy = true;
419 ChartBase *pret = NULL;
420 cnode = m_PatchList.GetFirst();
421 while (cnode && !cnode->GetData()->b_Valid) cnode = cnode->GetNext();
422 if (cnode && cnode->GetData()->b_Valid)
423 pret = ChartData->OpenChartFromDB(cnode->GetData()->dbIndex, FULL_INIT);
424
425 m_bbusy = false;
426 return pret;
427}
428
429ChartBase *Quilt::GetNextChart() {
430 if (!ChartData) return NULL;
431
432 if (!ChartData->IsValid()) return NULL;
433
434 if (m_bbusy) return NULL;
435
436 m_bbusy = true;
437 ChartBase *pret = NULL;
438 if (cnode) {
439 cnode = cnode->GetNext();
440 while (cnode && !cnode->GetData()->b_Valid) cnode = cnode->GetNext();
441 if (cnode && cnode->GetData()->b_Valid)
442 pret = ChartData->OpenChartFromDB(cnode->GetData()->dbIndex, FULL_INIT);
443 }
444
445 m_bbusy = false;
446 return pret;
447}
448
449ChartBase *Quilt::GetNextSmallerScaleChart() {
450 if (!ChartData) return NULL;
451
452 if (!ChartData->IsValid()) return NULL;
453
454 if (m_bbusy) return NULL;
455
456 m_bbusy = true;
457 ChartBase *pret = NULL;
458 if (cnode) {
459 cnode = cnode->GetPrevious();
460 while (cnode && !cnode->GetData()->b_Valid) cnode = cnode->GetPrevious();
461 if (cnode && cnode->GetData()->b_Valid)
462 pret = ChartData->OpenChartFromDB(cnode->GetData()->dbIndex, FULL_INIT);
463 }
464
465 m_bbusy = false;
466 return pret;
467}
468
469ChartBase *Quilt::GetLargestScaleChart() {
470 if (!ChartData) return NULL;
471
472 if (m_bbusy) return NULL;
473
474 m_bbusy = true;
475 ChartBase *pret = NULL;
476 cnode = m_PatchList.GetLast();
477 if (cnode)
478 pret = ChartData->OpenChartFromDB(cnode->GetData()->dbIndex, FULL_INIT);
479
480 m_bbusy = false;
481 return pret;
482}
483
484LLRegion Quilt::GetChartQuiltRegion(const ChartTableEntry &cte, ViewPort &vp) {
485 LLRegion chart_region;
486 LLRegion screen_region(vp.GetBBox());
487
488 // Special case for charts which extend around the world, or near to it
489 // Mostly this means cm93....
490 // Take the whole screen, clipped at +/- 80 degrees lat
491 if (fabs(cte.GetLonMax() - cte.GetLonMin()) > 180.) {
492 /*
493 int n_ply_entries = 4;
494 float ply[8];
495 ply[0] = 80.;
496 ply[1] = vp.GetBBox().GetMinX();
497 ply[2] = 80.;
498 ply[3] = vp.GetBBox().GetMaxX();
499 ply[4] = -80.;
500 ply[5] = vp.GetBBox().GetMaxX();
501 ply[6] = -80.;
502 ply[7] = vp.GetBBox().GetMinX();
503
504
505 OCPNRegion t_region = vp.GetVPRegionIntersect( screen_region, 4,
506 &ply[0], cte.GetScale() ); return t_region;
507 */
508 return LLRegion(-80, vp.GetBBox().GetMinLon(), 80,
509 vp.GetBBox().GetMaxLon());
510 }
511
512 // If the chart has an aux ply table, use it for finer region precision
513 int nAuxPlyEntries = cte.GetnAuxPlyEntries();
514 bool aux_ply_skipped = false;
515 if (nAuxPlyEntries >= 1) {
516 for (int ip = 0; ip < nAuxPlyEntries; ip++) {
517 int nAuxPly = cte.GetAuxCntTableEntry(ip);
518 if (nAuxPly > AUX_PLY_PERF_LIMIT) {
519 // wxLogMessage("PLY calculation skipped for %s, nAuxPly: %d",
520 // cte.GetpFullPath(), nAuxPly);
521 aux_ply_skipped = true;
522 break;
523 }
524 float *pfp = cte.GetpAuxPlyTableEntry(ip);
525 LLRegion t_region(nAuxPly, pfp);
526 t_region.Intersect(screen_region);
527 // OCPNRegion t_region = vp.GetVPRegionIntersect(
528 // screen_region, nAuxPly, pfp,
529 // cte.GetScale() );
530 if (!t_region.Empty()) chart_region.Union(t_region);
531 }
532 }
533
534 if (aux_ply_skipped || nAuxPlyEntries == 0) {
535 int n_ply_entries = cte.GetnPlyEntries();
536 float *pfp = cte.GetpPlyTable();
537
538 if (n_ply_entries >= 3) // could happen with old database and some charts,
539 // e.g. SHOM 2381.kap
540 {
541 LLRegion t_region(n_ply_entries, pfp);
542 t_region.Intersect(screen_region);
543 // const OCPNRegion t_region = vp.GetVPRegionIntersect(
544 // screen_region, n_ply_entries, pfp,
545 // cte.GetScale() );
546 if (!t_region.Empty()) chart_region.Union(t_region);
547
548 } else
549 chart_region = screen_region;
550 }
551
552 // Remove the NoCovr regions
553 int nNoCovrPlyEntries = cte.GetnNoCovrPlyEntries();
554 if (nNoCovrPlyEntries) {
555 for (int ip = 0; ip < nNoCovrPlyEntries; ip++) {
556 int nNoCovrPly = cte.GetNoCovrCntTableEntry(ip);
557 if (nNoCovrPly > NOCOVR_PLY_PERF_LIMIT) {
558 // wxLogMessage("NOCOVR calculation skipped for %s, nNoCovrPly: %d",
559 // cte.GetpFullPath(), nNoCovrPly);
560 continue;
561 }
562 float *pfp = cte.GetpNoCovrPlyTableEntry(ip);
563
564 LLRegion t_region(nNoCovrPly, pfp);
565 t_region.Intersect(screen_region);
566 // OCPNRegion t_region = vp.GetVPRegionIntersect(
567 // screen_region, nNoCovrPly, pfp,
568 // cte.GetScale()
569 // );
570
571 // We do a test removal of the NoCovr region.
572 // If the result iz empty, it must be that the NoCovr region is
573 // the full extent M_COVR(CATCOV=2) feature found in NOAA ENCs.
574 // We ignore it.
575
576 if (!t_region.Empty()) {
577 LLRegion test_region = chart_region;
578 test_region.Subtract(t_region);
579
580 if (!test_region.Empty()) chart_region = test_region;
581 }
582 }
583 }
584
585 // Another superbad hack....
586 // Super small scale raster charts like bluemarble.kap usually cross the
587 // prime meridian and Plypoints georef is problematic...... So, force full
588 // screen coverage in the quilt
589 if ((cte.GetScale() > 90000000) &&
590 (cte.GetChartFamily() == CHART_FAMILY_RASTER))
591 chart_region = screen_region;
592
593 // Clip the region to the current viewport
594 // chart_region.Intersect( vp.rv_rect ); already done
595
596 return chart_region;
597}
598
599bool Quilt::IsQuiltVector(void) {
600 if (m_bbusy) return false;
601
602 m_bbusy = true;
603
604 bool ret = false;
605
606 wxPatchListNode *cnode = m_PatchList.GetFirst();
607 while (cnode) {
608 if (cnode->GetData()) {
609 QuiltPatch *pqp = cnode->GetData();
610
611 if ((pqp->b_Valid) && (!pqp->b_eclipsed) &&
612 (pqp->dbIndex < ChartData->GetChartTableEntries())) {
613 const ChartTableEntry &ctei =
614 ChartData->GetChartTableEntry(pqp->dbIndex);
615
616 if (ctei.GetChartFamily() == CHART_FAMILY_VECTOR) {
617 ret = true;
618 break;
619 }
620 }
621 }
622 cnode = cnode->GetNext();
623 }
624
625 m_bbusy = false;
626 return ret;
627}
628
629bool Quilt::DoesQuiltContainPlugins(void) {
630 if (m_bbusy) return false;
631
632 m_bbusy = true;
633
634 bool ret = false;
635
636 wxPatchListNode *cnode = m_PatchList.GetFirst();
637 while (cnode) {
638 if (cnode->GetData()) {
639 QuiltPatch *pqp = cnode->GetData();
640
641 if ((pqp->b_Valid) && (!pqp->b_eclipsed)) {
642 const ChartTableEntry &ctei =
643 ChartData->GetChartTableEntry(pqp->dbIndex);
644
645 if (ctei.GetChartType() == CHART_TYPE_PLUGIN) {
646 ret = true;
647 break;
648 }
649 }
650 }
651 cnode = cnode->GetNext();
652 }
653
654 m_bbusy = false;
655 return ret;
656}
657
658int Quilt::GetChartdbIndexAtPix(ViewPort &VPoint, wxPoint p) {
659 if (m_bbusy) return -1;
660
661 m_bbusy = true;
662
663 double lat, lon;
664 VPoint.GetLLFromPix(p, &lat, &lon);
665
666 int ret = -1;
667
668 wxPatchListNode *cnode = m_PatchList.GetFirst();
669 while (cnode) {
670 if (cnode->GetData()->ActiveRegion.Contains(lat, lon)) {
671 ret = cnode->GetData()->dbIndex;
672 break;
673 } else
674 cnode = cnode->GetNext();
675 }
676
677 m_bbusy = false;
678 return ret;
679}
680
681ChartBase *Quilt::GetChartAtPix(ViewPort &VPoint, wxPoint p) {
682 if (m_bbusy) return NULL;
683
684 m_bbusy = true;
685
686 double lat, lon;
687 VPoint.GetLLFromPix(p, &lat, &lon);
688
689 // The patchlist is organized from small to large scale.
690 // We generally will want the largest scale chart at this point, so
691 // walk the whole list. The result will be the last one found, i.e. the
692 // largest scale chart.
693 ChartBase *pret = NULL;
694 wxPatchListNode *cnode = m_PatchList.GetFirst();
695 while (cnode) {
696 QuiltPatch *pqp = cnode->GetData();
697 if (!pqp->b_overlay && (pqp->ActiveRegion.Contains(lat, lon)))
698 if (ChartData->IsChartInCache(pqp->dbIndex)) {
699 pret = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
700 }
701 cnode = cnode->GetNext();
702 }
703
704 m_bbusy = false;
705 return pret;
706}
707
708ChartBase *Quilt::GetOverlayChartAtPix(ViewPort &VPoint, wxPoint p) {
709 if (m_bbusy) return NULL;
710
711 m_bbusy = true;
712
713 double lat, lon;
714 VPoint.GetLLFromPix(p, &lat, &lon);
715
716 // The patchlist is organized from small to large scale.
717 // We generally will want the largest scale chart at this point, so
718 // walk the whole list. The result will be the last one found, i.e. the
719 // largest scale chart.
720 ChartBase *pret = NULL;
721 wxPatchListNode *cnode = m_PatchList.GetFirst();
722 while (cnode) {
723 QuiltPatch *pqp = cnode->GetData();
724 if (pqp->b_overlay && (pqp->ActiveRegion.Contains(lat, lon)))
725 pret = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
726 cnode = cnode->GetNext();
727 }
728
729 m_bbusy = false;
730 return pret;
731}
732
733void Quilt::InvalidateAllQuiltPatchs(void) {
734 /*
735 if( m_bbusy )
736 return;
737
738 m_bbusy = true;
739 m_bbusy = false;
740 */
741 return;
742}
743
744std::vector<int> Quilt::GetQuiltIndexArray(void) {
745 return m_index_array;
746
747 std::vector<int> ret;
748
749 if (m_bbusy) return ret;
750
751 m_bbusy = true;
752
753 wxPatchListNode *cnode = m_PatchList.GetFirst();
754 while (cnode) {
755 ret.push_back(cnode->GetData()->dbIndex);
756 cnode = cnode->GetNext();
757 }
758
759 m_bbusy = false;
760
761 return ret;
762}
763
764bool Quilt::IsQuiltDelta(ViewPort &vp) {
765 if (!m_vp_quilt.IsValid() || !m_bcomposed) return true;
766
767 if (m_vp_quilt.view_scale_ppm != vp.view_scale_ppm) return true;
768
769 if (m_vp_quilt.m_projection_type != vp.m_projection_type) return true;
770
771 if (m_vp_quilt.rotation != vp.rotation) return true;
772
773 // Has the quilt shifted by more than one pixel in any direction?
774 wxPoint cp_last, cp_this;
775
776 cp_last = m_vp_quilt.GetPixFromLL(vp.clat, vp.clon);
777 cp_this = vp.GetPixFromLL(vp.clat, vp.clon);
778
779 return (cp_last != cp_this);
780}
781
782void Quilt::AdjustQuiltVP(ViewPort &vp_last, ViewPort &vp_proposed) {
783 if (m_bbusy) return;
784
785 // ChartBase *pRefChart = GetLargestScaleChart();
786 ChartBase *pRefChart =
787 ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
788
789 if (pRefChart) pRefChart->AdjustVP(vp_last, vp_proposed);
790}
791
792double Quilt::GetRefNativeScale() {
793 double ret_val = 1.0;
794 if (ChartData) {
795 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
796 if (pc) ret_val = pc->GetNativeScale();
797 }
798
799 return ret_val;
800}
801
802int Quilt::GetNewRefChart(void) {
803 // Using the current quilt, select a useable reference chart
804 // Said chart will be in the extended (possibly full-screen) stack,
805 // And will have a scale equal to or just greater than the current quilt
806 // reference scale, And will match current quilt projection type, and will
807 // have Skew=0, so as to be fully quiltable
808 int new_ref_dbIndex = m_refchart_dbIndex;
809 unsigned int im = m_extended_stack_array.size();
810 if (im > 0) {
811 for (unsigned int is = 0; is < im; is++) {
812 const ChartTableEntry &m =
813 ChartData->GetChartTableEntry(m_extended_stack_array[is]);
814
815 double skew_norm = m.GetChartSkew();
816 if (skew_norm > 180.) skew_norm -= 360.;
817
818 if ((m.Scale_ge(m_reference_scale)) &&
819 (m_reference_family == m.GetChartFamily()) &&
820 (m_bquiltanyproj || m_quilt_proj == m.GetChartProjectionType()) &&
821 (m_bquiltskew || (fabs(skew_norm) < 1.0))) {
822 new_ref_dbIndex = m_extended_stack_array[is];
823 break;
824 }
825 }
826 }
827 return new_ref_dbIndex;
828}
829
830int Quilt::GetNomScaleMax(int scale, ChartTypeEnum type,
831 ChartFamilyEnum family) {
832 switch (family) {
833 case CHART_FAMILY_RASTER: {
834 return scale / 4;
835 }
836
837 case CHART_FAMILY_VECTOR: {
838 return scale / 4;
839 }
840
841 default: {
842 return scale / 2;
843 }
844 }
845}
846
847int Quilt::GetNomScaleMin(int scale, ChartTypeEnum type,
848 ChartFamilyEnum family) {
849 double zoom_mod = (double)g_chart_zoom_modifier_raster;
850
851 if (family == CHART_FAMILY_VECTOR)
852 zoom_mod = (double)g_chart_zoom_modifier_vector;
853
854 double modf = zoom_mod / 5.; // -1->1
855 double mod = pow(16., modf);
856 mod = wxMax(mod, .2);
857 mod = wxMin(mod, 16.0);
858
859 // Apply zoom scale modifier according to chart family.
860 switch (family) {
861 case CHART_FAMILY_RASTER: {
862 if (CHART_TYPE_MBTILES == type)
863 // Here we don't apply the zoom modifier because MBTILES renderer
864 // changes the zoom layer accordingly so that the minimum scale does not
865 // change.
866 return scale * 4; // MBTiles are fast enough
867 else
868 return scale * 1 * mod;
869 }
870
871 case CHART_FAMILY_VECTOR: {
872 return scale * 4 * mod;
873 }
874
875 default: {
876 mod = wxMin(mod, 2.0);
877 return scale * 2 * mod;
878 }
879 }
880}
881
882struct scale {
883 int index, nom, min, max;
884};
885
886int Quilt::AdjustRefOnZoom(bool b_zin, ChartFamilyEnum family,
887 ChartTypeEnum type, double proposed_scale_onscreen) {
888 std::vector<scale> scales;
889 std::vector<scale> scales_mbtiles;
890
891 // For Vector charts, or for ZoomIN operations, we can switch to any chart
892 // that is on screen. Otherwise, we can only switch to charts contining the
893 // VP center point
894
895 // Since this rule is mainly for preservation of performance,
896 // we can also allow fullscreen reference chart selection if opengl is active
897
898 bool b_allow_fullscreen_ref =
899 (family == CHART_FAMILY_VECTOR) || b_zin || g_bopengl;
900
901 // Get the scale of the smallest scale
902 // chart, of the current type, in the quilt
903 int smallest_scale = 1;
904 for (size_t i = 0; i < m_extended_stack_array.size(); i++) {
905 int index = m_extended_stack_array[i];
906 if (ChartData->GetDBChartType(index) == type)
907 smallest_scale = wxMax(smallest_scale, ChartData->GetDBChartScale(index));
908 }
909
910 // Walk the extended chart array, capturing data
911 int i_first = 0;
912 for (size_t i = 0; i < m_extended_stack_array.size(); i++) {
913 int test_db_index = m_extended_stack_array[i];
914
915 if (b_allow_fullscreen_ref ||
916 m_parent->GetpCurrentStack()->DoesStackContaindbIndex(test_db_index)) {
917 if ((family == ChartData->GetDBChartFamily(test_db_index)) &&
918 IsChartQuiltableRef(test_db_index)
919 /*&& !IsChartS57Overlay( test_db_index )*/) {
920 int nscale = ChartData->GetDBChartScale(test_db_index);
921
922 int nmax_scale = GetNomScaleMax(nscale, type, family);
923
924 // For the largest scale chart, allow essentially infinite overzoom.
925 // The range will be clipped later
926 if (0 == i_first) nmax_scale = 1;
927
928 int nmin_scale = GetNomScaleMin(nscale, type, family);
929
930 // Allow RNC quilt to zoom far out and still show smallest scale chart.
931 if ((type == CHART_TYPE_KAP) && (nscale == smallest_scale))
932 nmin_scale *= 24;
933
934 // Allow MBTiles quilt to zoom far out and still show smallest scale
935 // chart.
936 if ((type == CHART_TYPE_MBTILES) && (nscale == smallest_scale))
937 nmin_scale *= 24;
938
939 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(test_db_index))
940 scales_mbtiles.push_back(
941 scale{test_db_index, nscale, nmin_scale, nmax_scale});
942 else
943 scales.push_back(
944 scale{test_db_index, nscale, nmin_scale, nmax_scale});
945
946 i_first++;
947 }
948 }
949 }
950 // mbtiles charts only set
951 if (scales.empty()) scales = scales_mbtiles;
952 // If showing Vector charts,
953 // Find the smallest scale chart of the target type (i.e. skipping cm93)
954 // and make sure that its min scale is at least
955 // small enough to allow reasonable zoomout.
956 // But this will be calculated without regard to zoom scale factor, so that
957 // the piano does not grow excessively
958 if (CHART_FAMILY_VECTOR == family) {
959 for (size_t i = scales.size(); i; i--) {
960 int test_db_index = scales[i - 1].index;
961 if (type == ChartData->GetDBChartType(test_db_index)) {
962 scales[i - 1].min = scales[i - 1].nom * 80;
963 break;
964 }
965 }
966 }
967
968 // Traverse the list, making sure that the allowable scale ranges overlap so
969 // as to make no "holes" in the coverage. We do this by extending upward the
970 // range of larger scale charts, so that they overlap the next smaller scale
971 // chart. Makes a nicer image... However, we don't want excessive underzoom,
972 // for performance reasons. So make sure any adjusted min_scale is not more
973 // than twice the already established value
974 if (scales.size() > 1) {
975 for (unsigned i = 0; i < scales.size() - 1; i++) {
976 int min_scale_test = wxMax(scales[i].min, scales[i + 1].max + 1);
977 min_scale_test = wxMin(min_scale_test, scales[i].min * 2);
978 scales[i].min = min_scale_test;
979 // min_scale[i] = wxMax(min_scale[i], max_scale.Item(i+1) +
980 // 1);
981 }
982 }
983
984 // There may still be holes...
985 // Traverse the list again, from smaller to larger scale, filling in any holes
986 // by increasing the max_scale of smaller scale charts. Skip cm93 if present
987 if (scales.size() > 2) {
988 for (size_t i = scales.size() - 2; i >= 1; i--) {
989 scales[i].max = wxMin(scales[i].max, scales[i - 1].min - 1);
990 }
991 }
992
993 int new_ref_dbIndex = -1;
994
995 // Search for the largest scale chart whose scale limits contain the requested
996 // scale.
997 for (size_t i = 0; i < scales.size(); i++) {
998 if ((proposed_scale_onscreen <
999 scales[i].min *
1000 1.05) && // 5 percent leeway to allow for roundoff errors
1001 (proposed_scale_onscreen > scales[i].max)) {
1002 new_ref_dbIndex = scales[i].index;
1003 break;
1004 }
1005 }
1006
1007 return new_ref_dbIndex;
1008}
1009
1010int Quilt::AdjustRefOnZoomOut(double proposed_scale_onscreen) {
1011 // Reset "lost" chart logic
1012 m_lost_refchart_dbIndex = -1;
1013
1014 int current_db_index = m_refchart_dbIndex;
1015 int current_family = m_reference_family;
1016 ChartTypeEnum current_type = (ChartTypeEnum)m_reference_type;
1017
1018 if (m_refchart_dbIndex >= 0) {
1019 const ChartTableEntry &cte =
1020 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1021 current_family = cte.GetChartFamily();
1022 current_type = (ChartTypeEnum)cte.GetChartType();
1023 }
1024
1025 if (current_type == CHART_TYPE_CM93COMP) return current_db_index;
1026
1027 int proposed_ref_index =
1028 AdjustRefOnZoom(false, (ChartFamilyEnum)current_family, current_type,
1029 proposed_scale_onscreen);
1030
1031 m_zout_family = -1;
1032 if (proposed_ref_index < 0) {
1033 m_zout_family = current_family; // save it
1034 m_zout_type = current_type;
1035 m_zout_dbindex = current_db_index;
1036 }
1037
1038 SetReferenceChart(proposed_ref_index);
1039
1040 return proposed_ref_index;
1041}
1042
1043int Quilt::AdjustRefOnZoomIn(double proposed_scale_onscreen) {
1044 // Reset "lost" chart logic
1045 m_lost_refchart_dbIndex = -1;
1046
1047 int current_db_index = m_refchart_dbIndex;
1048 int current_family = m_reference_family;
1049 ChartTypeEnum current_type = (ChartTypeEnum)m_reference_type;
1050
1051 if (m_zout_family >= 0) {
1052 current_type = (ChartTypeEnum)m_zout_type;
1053 current_family = m_zout_family;
1054 }
1055
1056 // If the current reference chart is cm93, and it became so due to a zout
1057 // from another family, detect this case and allow switch to save chart index
1058 // family
1059 if (current_type == CHART_TYPE_CM93COMP) {
1060 if (m_zout_family >= 0) {
1061 current_family = ChartData->GetDBChartFamily(m_zout_dbindex);
1062 } else // cm93 (selected) does not shift charts
1063 return current_db_index;
1064 }
1065
1066 if ((-1 == m_refchart_dbIndex) && (m_zout_dbindex >= 0))
1067 BuildExtendedChartStackAndCandidateArray(m_zout_dbindex, m_vp_quilt);
1068 else
1069 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, m_vp_quilt);
1070
1071 int proposed_ref_index =
1072 AdjustRefOnZoom(true, (ChartFamilyEnum)current_family, current_type,
1073 proposed_scale_onscreen);
1074
1075 if (current_db_index == -1) {
1076 SetReferenceChart(proposed_ref_index);
1077 return proposed_ref_index;
1078 }
1079
1080 if (proposed_ref_index != -1) {
1081 if (ChartData->GetDBChartScale(current_db_index) >=
1082 ChartData->GetDBChartScale(proposed_ref_index)) {
1083 SetReferenceChart(proposed_ref_index);
1084 return proposed_ref_index;
1085 }
1086 }
1087 proposed_ref_index = current_db_index;
1088
1089 SetReferenceChart(proposed_ref_index);
1090
1091 return proposed_ref_index;
1092}
1093
1094bool Quilt::IsChartSmallestScale(int dbIndex) {
1095 if (!ChartData) return false;
1096
1097 // find the smallest scale chart of the specified type on the extended stack
1098 // array
1099 int specified_type = ChartData->GetDBChartType(dbIndex);
1100 int target_dbindex = -1;
1101
1102 unsigned int target_stack_index = 0;
1103 if (m_extended_stack_array.size()) {
1104 while ((target_stack_index <= (m_extended_stack_array.size() - 1))) {
1105 int test_db_index = m_extended_stack_array[target_stack_index];
1106
1107 if (specified_type == ChartData->GetDBChartType(test_db_index))
1108 target_dbindex = test_db_index;
1109
1110 target_stack_index++;
1111 }
1112 }
1113 return (dbIndex == target_dbindex);
1114}
1115
1116LLRegion Quilt::GetHiliteRegion() {
1117 LLRegion r;
1118
1119 // TODO Idea: convert this to an array of smaller regions. Should be faster
1120 // to compose...
1121
1122 for (auto &index : m_HiLiteIndexArray) {
1123 const ChartTableEntry &cte = ChartData->GetChartTableEntry(index);
1124 LLRegion cell_region = GetChartQuiltRegion(cte, m_vp_quilt);
1125 r.Union(cell_region);
1126#if 0
1127 xxx
1128 // Walk the PatchList, looking for the target hilite index
1129 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
1130 wxPatchListNode *pcinode = m_PatchList.Item(i);
1131 QuiltPatch *piqp = pcinode->GetData();
1132 if ((index == piqp->dbIndex) && (piqp->b_Valid)) // found it
1133 {
1134 r.Union(piqp->ActiveRegion);
1135 }
1136 }
1137#endif
1138 }
1139 return r;
1140}
1141
1142#if 0
1143LLRegion Quilt::GetHiliteRegion() {
1144 LLRegion r;
1145 if (m_nHiLiteIndex >= 0) {
1146 // Walk the PatchList, looking for the target hilite index
1147 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
1148 wxPatchListNode *pcinode = m_PatchList.Item(i);
1149 QuiltPatch *piqp = pcinode->GetData();
1150 if ((m_nHiLiteIndex == piqp->dbIndex) && (piqp->b_Valid)) // found it
1151 {
1152 r = piqp->ActiveRegion;
1153 break;
1154 }
1155 }
1156
1157 // If not in the patchlist, look in the full chartbar
1158 if (r.Empty()) {
1159 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1160 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1161 if (m_nHiLiteIndex == pqc->dbIndex) {
1162 LLRegion chart_region = pqc->GetCandidateRegion();
1163 if (!chart_region.Empty()) {
1164 // Do not highlite fully eclipsed charts
1165 bool b_eclipsed = false;
1166 for (unsigned int ir = 0; ir < m_eclipsed_stack_array.size();
1167 ir++) {
1168 if (m_nHiLiteIndex == m_eclipsed_stack_array[ir]) {
1169 b_eclipsed = true;
1170 break;
1171 }
1172 }
1173
1174 if (!b_eclipsed) r = chart_region;
1175 break;
1176 }
1177 }
1178 }
1179 }
1180
1181 // Might be MBTiles....
1182 if (r.Empty()) {
1183 const ChartTableEntry &cte =
1184 ChartData->GetChartTableEntry(m_nHiLiteIndex);
1185 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
1186 r = GetTilesetRegion(m_nHiLiteIndex);
1187 }
1188 }
1189 }
1190 return r;
1191}
1192#endif
1193
1194const LLRegion &Quilt::GetTilesetRegion(int dbIndex) {
1195 LLRegion world_region(-90, -180, 90, 180);
1196
1197 const ChartTableEntry &cte = ChartData->GetChartTableEntry(dbIndex);
1198 LLRegion &target_region = const_cast<LLRegion &>(cte.quilt_candidate_region);
1199
1200 if (!target_region.Empty()) return target_region;
1201
1202 // If the chart has an aux ply table, use it for finer region precision
1203 int nAuxPlyEntries = cte.GetnAuxPlyEntries();
1204 if (nAuxPlyEntries >= 1) {
1205 target_region.Clear();
1206 for (int ip = 0; ip < nAuxPlyEntries; ip++) {
1207 float *pfp = cte.GetpAuxPlyTableEntry(ip);
1208 int nAuxPly = cte.GetAuxCntTableEntry(ip);
1209
1210 target_region.Union(LLRegion(nAuxPly, pfp));
1211 }
1212 } else {
1213 std::vector<float> vec = ChartData->GetReducedPlyPoints(dbIndex);
1214
1215 std::vector<float> vecr;
1216 for (size_t i = 0; i < vec.size() / 2; i++) {
1217 float a = vec[i * 2 + 1];
1218 vecr.push_back(a);
1219 a = vec[i * 2];
1220 vecr.push_back(a);
1221 }
1222
1223 std::vector<float>::iterator it = vecr.begin();
1224
1225 if (vecr.size() / 2 >= 3) { // could happen with old database and some
1226 // charts, e.g. SHOM 2381.kap
1227
1228 target_region = LLRegion(vecr.size() / 2, (float *)&(*it));
1229 } else
1230 target_region = world_region;
1231 }
1232
1233 // Remove the NoCovr regions
1234 if (!target_region.Empty()) { // don't bother if the region is already empty
1235 int nNoCovrPlyEntries = cte.GetnNoCovrPlyEntries();
1236 if (nNoCovrPlyEntries) {
1237 for (int ip = 0; ip < nNoCovrPlyEntries; ip++) {
1238 float *pfp = cte.GetpNoCovrPlyTableEntry(ip);
1239 int nNoCovrPly = cte.GetNoCovrCntTableEntry(ip);
1240
1241 LLRegion t_region = LLRegion(nNoCovrPly, pfp);
1242
1243 // We do a test removal of the NoCovr region.
1244 // If the result iz empty, it must be that the NoCovr region is
1245 // the full extent M_COVR(CATCOV=2) feature found in NOAA ENCs.
1246 // We ignore it.
1247
1248 if (!t_region.Empty()) {
1249 LLRegion test_region = target_region;
1250 test_region.Subtract(t_region);
1251
1252 if (!test_region.Empty()) target_region = test_region;
1253 }
1254 }
1255 }
1256 }
1257
1258 // Another superbad hack....
1259 // Super small scale raster charts like bluemarble.kap usually cross the
1260 // prime meridian and Plypoints georef is problematic...... So, force full
1261 // screen coverage in the quilt
1262 // if( (cte.GetScale() > 90000000) && (cte.GetChartFamily() ==
1263 // CHART_FAMILY_RASTER) )
1264 // target_region = world_region;
1265
1266 return target_region;
1267}
1268
1269bool Quilt::BuildExtendedChartStackAndCandidateArray(int ref_db_index,
1270 ViewPort &vp_in) {
1271 double zoom_test_val = .002;
1272 zoom_test_val *= 2;
1273
1274 EmptyCandidateArray();
1275 m_extended_stack_array.clear();
1276 m_fullscreen_index_array.clear();
1277
1278 int reference_scale = 1e8;
1279 int reference_type = -1;
1280 int reference_family = -1;
1281 int quilt_proj =
1282 m_bquiltanyproj ? vp_in.m_projection_type : PROJECTION_UNKNOWN;
1283
1284 if (ref_db_index >= 0) {
1285 const ChartTableEntry &cte_ref =
1286 ChartData->GetChartTableEntry(ref_db_index);
1287 reference_scale = cte_ref.GetScale();
1288 reference_type = cte_ref.GetChartType();
1289 if (!m_bquiltanyproj) quilt_proj = ChartData->GetDBChartProj(ref_db_index);
1290 reference_family = cte_ref.GetChartFamily();
1291 }
1292
1293 bool b_need_resort = false;
1294
1295 ViewPort vp_local = vp_in; // non-const copy
1296
1297 // if( !pCurrentStack ) {
1298 // pCurrentStack = new ChartStack;
1299 // ChartData->BuildChartStack( pCurrentStack, vp_local.clat,
1300 // vp_local.clon );
1301 // }
1302
1303 int n_charts = m_parent->GetpCurrentStack()->nEntry;
1304
1305 // Walk the current ChartStack...
1306 // Building the quilt candidate array
1307 for (int ics = 0; ics < n_charts; ics++) {
1308 int istack = m_parent->GetpCurrentStack()->GetDBIndex(ics);
1309 if (istack < 0) continue;
1310 m_extended_stack_array.push_back(istack);
1311
1312 // If the reference chart is cm93, we need not add any charts to the
1313 // candidate array from the vp center. All required charts will be added
1314 // (by family) as we consider the entire database (group) and full screen
1315 // later
1316 if (reference_type == CHART_TYPE_CM93COMP) continue;
1317
1318 const ChartTableEntry &cte = ChartData->GetChartTableEntry(istack);
1319
1320 // only charts of the proper projection and type may be quilted....
1321 // Also, only unskewed charts if so directed
1322 // and we avoid adding CM93 Composite until later
1323
1324 // If any PlugIn charts are involved, we make the inclusion test on chart
1325 // family, instead of chart type.
1326 if ((cte.GetChartType() == CHART_TYPE_PLUGIN) ||
1327 (reference_type == CHART_TYPE_PLUGIN)) {
1328 if (reference_family != cte.GetChartFamily()) {
1329 continue;
1330 }
1331 } else {
1332 if (reference_type != cte.GetChartType()) {
1333 continue;
1334 }
1335 }
1336
1337 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
1338
1339 // Also, check the scale of the proposed chart. If too small, skip it.
1340 int candidate_chart_scale = cte.GetScale();
1341 double chart_native_ppm =
1342 m_canvas_scale_factor / (double)candidate_chart_scale;
1343 double zoom_factor = vp_local.view_scale_ppm / chart_native_ppm;
1344 if ((zoom_factor < zoom_test_val) &&
1345 // MBTILES charts report the scale of their smallest layer (i.e. most
1346 // detailed) as native chart scale, even if they are embedding many more
1347 // layers. Since we don't know their maximum scale at this stage, we
1348 // don't skip the chart if this native scale is apparently too small.
1349 (cte.GetChartType() != CHART_TYPE_MBTILES)) {
1350 m_extended_stack_array.pop_back();
1351 continue;
1352 }
1353
1354 double skew_norm = cte.GetChartSkew();
1355 if (skew_norm > 180.) skew_norm -= 360.;
1356
1357 if ((m_bquiltskew ? 1 : fabs(skew_norm) < 1.0) &&
1358 (m_bquiltanyproj || cte.GetChartProjectionType() == quilt_proj)) {
1359 QuiltCandidate *qcnew = new QuiltCandidate;
1360 qcnew->dbIndex = istack;
1361 qcnew->SetScale(cte.GetScale());
1362 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1363 }
1364
1365 // if( ( reference_type == cte.GetChartType() ) ||
1366 // ( (cte.GetChartType() == CHART_TYPE_PLUGIN ) &&
1367 // (reference_family == cte.GetChartFamily() )) || (
1368 // (reference_type == CHART_TYPE_PLUGIN ) &&
1369 // (reference_family == cte.GetChartFamily() )) ){
1370 //
1371 // if( ( m_bquiltskew ? 1: fabs( skew_norm ) < 1.0 )
1372 // && ( m_bquiltanyproj || cte.GetChartProjectionType()
1373 // == quilt_proj )
1374 // && ( cte.GetChartType() != CHART_TYPE_CM93COMP ) ) {
1375 // QuiltCandidate *qcnew = new QuiltCandidate;
1376 // qcnew->dbIndex = i;
1377 // qcnew->ChartScale = cte.GetScale();
1378 //
1379 // m_pcandidate_array->push_back( qcnew ); //
1380 // auto-sorted on scale
1381 // }
1382 // }
1383 }
1384
1385 // Search the entire database, potentially adding all charts
1386 // which intersect the ViewPort in any way
1387 // .AND. other requirements.
1388 // Again, skipping cm93 for now
1389 int n_all_charts = ChartData->GetChartTableEntries();
1390
1391 LLBBox viewbox = vp_local.GetBBox();
1392 int sure_index = -1;
1393 int sure_index_scale = 0;
1394 int sure_index_type = -1;
1395
1396 for (int i = 0; i < n_all_charts; i++) {
1397 // We can eliminate some charts immediately
1398 // Try to make these tests in some sensible order....
1399
1400 int groupIndex = m_parent->m_groupIndex;
1401 if ((groupIndex > 0) && (!ChartData->IsChartInGroup(i, groupIndex)))
1402 continue;
1403
1404 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1405
1406 // Skip any charts in Exclude array
1407 if (ChartData->IsChartDirectoryExcluded(cte.GetFullPath())) continue;
1408
1409 if (cte.GetChartType() == CHART_TYPE_CM93COMP)
1410 m_fullscreen_index_array.push_back(i);
1411
1412 // On android, SDK > 29, we require that the directory of charts be
1413 // "writable" as determined by Android Java file system
1414#ifdef __OCPN__ANDROID__
1415 wxFileName fn(cte.GetFullSystemPath());
1416 if (!androidIsDirWritable(fn.GetPath())) continue;
1417#endif
1418 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
1419
1420 const LLBBox &chart_box = cte.GetBBox();
1421 if ((viewbox.IntersectOut(chart_box))) continue;
1422
1423 if (cte.GetChartType() == CHART_TYPE_PLUGIN) {
1424 if (!ChartData->IsChartAvailable(i)) continue;
1425 }
1426
1427 m_fullscreen_index_array.push_back(i);
1428
1429 if (reference_family != cte.GetChartFamily()) {
1430 if (cte.GetChartType() != CHART_TYPE_MBTILES) continue;
1431 }
1432
1433 if (!m_bquiltanyproj && quilt_proj != cte.GetChartProjectionType())
1434 continue;
1435
1436 double skew_norm = cte.GetChartSkew();
1437 if (skew_norm > 180.) skew_norm -= 360.;
1438
1439 if (!m_bquiltskew && fabs(skew_norm) > 1.0) continue;
1440
1441 // Special case for S57 ENC
1442 // Add the chart only if the chart's fractional area exceeds n%
1443#if 0
1444 if( CHART_TYPE_S57 == cte.GetChartType() ) {
1445 //Get the fractional area of this candidate
1446 double chart_area = (cte.GetLonMax() - cte.GetLonMin()) *
1447 (cte.GetLatMax() - cte.GetLatMin());
1448 double quilt_area = viewbox.GetLonRange() * viewbox.GetLatRange();
1449 if ((chart_area / quilt_area) < .01) continue;
1450 }
1451#endif
1452 int candidate_chart_scale = cte.GetScale();
1453
1454 // Try to guarantee that there is one chart added with scale larger than
1455 // reference scale
1456 // Take note here, and keep track of the smallest scale chart that is
1457 // larger scale than reference....
1458 if (!cte.Scale_ge(reference_scale)) {
1459 if (cte.Scale_gt(sure_index_scale)) {
1460 sure_index = i;
1461 sure_index_scale = candidate_chart_scale;
1462 sure_index_type = cte.GetChartType();
1463 }
1464 }
1465
1466 // At this point, the candidate is the right type, skew, and projection,
1467 // and is on-screen somewhere.... Now add the candidate if its scale is
1468 // smaller than the reference scale, or is not excessively underzoomed.
1469
1470 // Calculate zoom factor for this chart
1471 double chart_native_ppm =
1472 m_canvas_scale_factor / (double)candidate_chart_scale;
1473 double zoom_factor = vp_in.view_scale_ppm / chart_native_ppm;
1474 double zoom_factor_test_extra = 0.2;
1475
1476 // For some quilts (e.g.cm93 + MBTiles), the reference scale is not known
1477 // or is default 1e8 value. This would exclude large scale MBtiles.
1478 // Adjust the reference scale test value so as to include the MBTiles,
1479 // if their zoom factor is suitable
1480 double ref_scale_test = reference_scale;
1481 if (cte.GetChartType() == CHART_TYPE_MBTILES)
1482 ref_scale_test = candidate_chart_scale;
1483
1484 if ((cte.Scale_ge(ref_scale_test) && (zoom_factor > zoom_test_val)) ||
1485 (zoom_factor > zoom_factor_test_extra)) {
1486 LLRegion cell_region = GetChartQuiltRegion(cte, vp_local);
1487
1488 // this is false if the chart has no actual overlap on screen
1489 // or lots of NoCovr regions. US3EC04.000 is a good example
1490 // i.e the full bboxes overlap, but the actual vp intersect is null.
1491 if (!cell_region.Empty()) {
1492 // Check to see if this chart is already in the stack array
1493 // by virtue of being under the Viewport center point....
1494 bool b_exists = false;
1495 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1496 if (i == m_extended_stack_array[ir]) {
1497 b_exists = true;
1498 break;
1499 }
1500 }
1501
1502 if (!b_exists) {
1503 // Check to be sure that this chart has not already been added
1504 // i.e. charts that have exactly the same file name and nearly the
1505 // same mod time These charts can be in the database due to having
1506 // the exact same chart in different directories, as may be desired
1507 // for some grouping schemes
1508 // Extended to also check for "identical" charts, having exact same
1509 // EditionDate
1510 bool b_noadd = false;
1511 ChartTableEntry *pn = ChartData->GetpChartTableEntry(i);
1512 for (unsigned int id = 0; id < m_extended_stack_array.size(); id++) {
1513 if (m_extended_stack_array[id] != -1) {
1514 ChartTableEntry *pm =
1515 ChartData->GetpChartTableEntry(m_extended_stack_array[id]);
1516 bool bsameTime = false;
1517 if (pm->GetFileTime() && pn->GetFileTime()) {
1518 if (labs(pm->GetFileTime() - pn->GetFileTime()) < 60)
1519 bsameTime = true;
1520 }
1521 if (pm->GetChartEditionDate() == pn->GetChartEditionDate())
1522 bsameTime = true;
1523
1524 if (bsameTime) {
1525 if (pn->GetpFileName()->IsSameAs(*(pm->GetpFileName())))
1526 b_noadd = true;
1527 }
1528 }
1529 }
1530
1531 if (!b_noadd) {
1532 m_extended_stack_array.push_back(i);
1533
1534 QuiltCandidate *qcnew = new QuiltCandidate;
1535 qcnew->dbIndex = i;
1536 qcnew->SetScale(
1537 candidate_chart_scale); // ChartData->GetDBChartScale( i );
1538
1539 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1540
1541 b_need_resort = true;
1542 }
1543 }
1544 }
1545 }
1546 } // for all charts
1547
1548 // Check to be sure that at least one chart was added that is larger scale
1549 // than reference scale
1550 if (-1 != sure_index) {
1551 // check to see if it is already in
1552 bool sure_exists = false;
1553 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1554 if (sure_index == m_extended_stack_array[ir]) {
1555 sure_exists = true;
1556 break;
1557 }
1558 }
1559
1560 // If not already added, do so now
1561 if (!sure_exists && (sure_index_type != CHART_TYPE_MBTILES)) {
1562 m_extended_stack_array.push_back(sure_index);
1563
1564 QuiltCandidate *qcnew = new QuiltCandidate;
1565 qcnew->dbIndex = sure_index;
1566 qcnew->SetScale(ChartData->GetDBChartScale(sure_index));
1567 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1568
1569 b_need_resort = true;
1570 }
1571 }
1572
1573 // Re sort the extended stack array on scale
1574 if (b_need_resort && m_extended_stack_array.size() > 1) {
1575 std::sort(m_extended_stack_array.begin(), m_extended_stack_array.end(),
1576 CompareScalesStd);
1577 }
1578 return true;
1579}
1580
1581int Quilt::AdjustRefSelection(const ViewPort &vp_in) {
1582 // Starting from the currently selected Ref chart,
1583 // choose a ref chart that meets the required under/overzoom limits
1584 // It might be the same, so no change required
1585
1586 if (!ChartData) return false;
1587
1588 if (ChartData
1589 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1590 return false;
1591
1592 ViewPort vp_local = vp_in; // need a non-const copy
1593
1594 // As ChartdB data is always in rectilinear space, region calculations need
1595 // to be done with no VP rotation
1596 vp_local.SetRotationAngle(0.);
1597
1598 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1599
1600 ChartFamilyEnum family = CHART_FAMILY_RASTER;
1601 ChartTypeEnum type = CHART_TYPE_KAP;
1602
1603 // Get the desired family/type
1604 if (m_refchart_dbIndex >= 0) {
1605 const ChartTableEntry &cte_ref =
1606 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1607 type = (ChartTypeEnum)cte_ref.GetChartType();
1608 family = (ChartFamilyEnum)cte_ref.GetChartFamily();
1609 }
1610
1611 int ret_index = AdjustRefOnZoom(true, family, type, vp_in.chart_scale);
1612
1613 return ret_index;
1614}
1615
1616double Quilt::GetBestStartScale(int dbi_ref_hint, const ViewPort &vp_in) {
1617 if (!ChartData) return vp_in.view_scale_ppm;
1618
1619 if (ChartData
1620 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1621 return vp_in.view_scale_ppm;
1622
1623 ViewPort vp_local = vp_in; // need a non-const copy
1624
1625 // Validate Reference Chart hint
1626 int tentative_ref_index = dbi_ref_hint;
1627 if (dbi_ref_hint < 0) {
1628 // arbitrarily select reference chart as largest scale on current stack
1629 // if( !pCurrentStack ) {
1630 // pCurrentStack = new ChartStack;
1631 // ChartData->BuildChartStack( pCurrentStack, vp_local.clat,
1632 // vp_local.clon );
1633 // }
1634 tentative_ref_index = m_parent->GetpCurrentStack()->GetDBIndex(0);
1635 }
1636
1637 // As ChartdB data is always in rectilinear space, region calculations need
1638 // to be done with no VP rotation
1639 // double saved_vp_rotation = vp_local.rotation; // save
1640 // a copy
1641 vp_local.SetRotationAngle(0.);
1642
1643 BuildExtendedChartStackAndCandidateArray(tentative_ref_index, vp_local);
1644
1645 // tentative choice might not be in the extended stack....
1646 bool bf = false;
1647 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1648 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1649 if (qc->dbIndex == tentative_ref_index) {
1650 bf = true;
1651 break;
1652 }
1653 }
1654
1655 if (!bf && m_pcandidate_array->GetCount()) {
1656 tentative_ref_index = GetNewRefChart();
1657 BuildExtendedChartStackAndCandidateArray(tentative_ref_index, vp_local);
1658 }
1659
1660 double proposed_scale_onscreen = vp_in.chart_scale;
1661
1662 if (m_pcandidate_array->GetCount()) {
1663 m_refchart_dbIndex = tentative_ref_index;
1664 } else {
1665 // Need to choose some chart, find a quiltable candidate
1666 bool bfq = false;
1667 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1668 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1669 if (IsChartQuiltableRef(qc->dbIndex)) {
1670 m_refchart_dbIndex = qc->dbIndex;
1671 bfq = true;
1672 break;
1673 }
1674 }
1675
1676 if (!bfq) // fallback to first chart in stack
1677 m_refchart_dbIndex = m_parent->GetpCurrentStack()->GetDBIndex(0);
1678 }
1679
1680 if (m_refchart_dbIndex >= 0) {
1681 // Suggest a scale so that the largest scale candidate is "nominally"
1682 // scaled, meaning not overzoomed, and not underzoomed
1683 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
1684 if (pc) {
1685 double min_ref_scale =
1686 pc->GetNormalScaleMin(m_parent->GetCanvasScaleFactor(), false);
1687 double max_ref_scale = pc->GetNormalScaleMax(
1688 m_parent->GetCanvasScaleFactor(), m_canvas_width);
1689 if ((proposed_scale_onscreen >= min_ref_scale) &&
1690 (proposed_scale_onscreen <= max_ref_scale))
1691 return vp_in.view_scale_ppm;
1692 else {
1693 proposed_scale_onscreen = wxMin(proposed_scale_onscreen, max_ref_scale);
1694 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, min_ref_scale);
1695 }
1696 }
1697 }
1698 return m_parent->GetCanvasScaleFactor() / proposed_scale_onscreen;
1699}
1700
1701ChartBase *Quilt::GetRefChart() {
1702 if (m_refchart_dbIndex >= 0 && ChartData)
1703 return ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
1704 return nullptr;
1705}
1706
1707void Quilt::UnlockQuilt() {
1708 wxASSERT(m_bbusy == false);
1709 ChartData->UnLockCache();
1710 // unlocked only charts owned by the Quilt
1711 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1712 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1713 ChartData->UnLockCacheChart(pqc->dbIndex);
1714 }
1715}
1716
1717bool Quilt::Compose(const ViewPort &vp_in) {
1718 if (!ChartData) return false;
1719
1720 if (ChartData
1721 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1722 return false;
1723
1724 if (!m_parent->GetpCurrentStack()) return false;
1725
1726 if (m_bbusy) return false;
1727
1728 // XXX call before setting m_bbusy for wxASSERT in UnlockQuilt
1729 UnlockQuilt();
1730 m_bbusy = true;
1731
1732 ViewPort vp_local = vp_in; // need a non-const copy
1733
1734 // Get Reference Chart parameters
1735 if (m_refchart_dbIndex >= 0) {
1736 const ChartTableEntry &cte_ref =
1737 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1738 m_reference_scale = cte_ref.GetScale();
1739 m_reference_type = cte_ref.GetChartType();
1740 if (!m_bquiltanyproj)
1741 m_quilt_proj = ChartData->GetDBChartProj(m_refchart_dbIndex);
1742 m_reference_family = cte_ref.GetChartFamily();
1743 }
1744
1745 // Set up the viewport projection type
1746 if (!m_bquiltanyproj) vp_local.SetProjectionType(m_quilt_proj);
1747
1748 // As ChartdB data is always in rectilinear space, region calculations need
1749 // to be done with no VP rotation
1750 // double saved_vp_rotation = vp_local.rotation; //
1751 // save a copy vp_local.SetRotationAngle( 0. );
1752
1753 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1754
1755 // It can happen (in groups switch, or single->quilt mode) that there
1756 // is no refchart known, but there are charts available in the piano.
1757 // Detect this case, and build the quilt based on the smallest scale chart
1758 // anywhere on screen.
1759
1760 // if ((m_refchart_dbIndex < 0) && m_extended_stack_array.size()){
1761 // // Take the smallest scale chart in the array.
1762 // int tentative_dbIndex = m_extended_stack_array.back();
1763 //
1764 // // Verify that the zoom scale is acceptable.
1765 // const ChartTableEntry &cte =
1766 // ChartData->GetChartTableEntry(tentative_dbIndex); int
1767 // candidate_chart_scale = cte.GetScale(); double chart_native_ppm =
1768 // m_canvas_scale_factor / (double)candidate_chart_scale;
1769 // double zoom_factor = vp_local.view_scale_ppm / chart_native_ppm;
1770 // if (zoom_factor > 0.1){
1771 // m_refchart_dbIndex = tentative_dbIndex;
1772 // BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex,
1773 // vp_local);
1774 // }
1775 // }
1776
1777 // It is possible that the reference chart is not really part of the
1778 // visible quilt This can happen when the reference chart is panned
1779 // off-screen in full screen quilt mode
1780 // If this situation occurs, we need to immediately select a new reference
1781 // chart And rebuild the Candidate Array
1782 //
1783 // We also save the dbIndex of the "lost" chart, and try to recover it
1784 // on subsequent quilts, typically as the user pans the "lost" chart back
1785 // on-screen. The "lost" chart logic is reset on any zoom operations. See
1786 // FS#1221
1787 //
1788 // A special case occurs with cm93 composite chart set as the reference
1789 // chart: It is not at this point a candidate, so won't be found by the
1790 // search This case is indicated if the candidate count is zero. If so, do
1791 // not invalidate the ref chart
1792 bool bf = false;
1793 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1794 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1795 if (qc->dbIndex == m_refchart_dbIndex) {
1796 bf = true;
1797 break;
1798 }
1799 }
1800
1801 if (!bf && m_pcandidate_array->GetCount() &&
1802 (m_reference_type != CHART_TYPE_CM93COMP)) {
1803 m_lost_refchart_dbIndex = m_refchart_dbIndex; // save for later
1804 int candidate_ref_index = GetNewRefChart();
1805 if (m_refchart_dbIndex != candidate_ref_index) {
1806 m_refchart_dbIndex = candidate_ref_index;
1807 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1808 }
1809 // There was no viable candidate of smaller scale than the "lost
1810 // chart", so choose the smallest scale chart in the candidate list.
1811 else {
1812 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1813 if (m_pcandidate_array->GetCount()) {
1814 m_refchart_dbIndex =
1815 m_pcandidate_array->Item(m_pcandidate_array->GetCount() - 1)
1816 ->dbIndex;
1817 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1818 }
1819 }
1820 }
1821
1822 if ((-1 != m_lost_refchart_dbIndex) &&
1823 (m_lost_refchart_dbIndex != m_refchart_dbIndex)) {
1824 // Is the lost chart in the extended stack ?
1825 // If so, build a new Cnadidate array based upon the lost chart
1826 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1827 if (m_lost_refchart_dbIndex == m_extended_stack_array[ir]) {
1828 m_refchart_dbIndex = m_lost_refchart_dbIndex;
1829 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1830 m_lost_refchart_dbIndex = -1;
1831 break;
1832 }
1833 }
1834 }
1835
1836 bool b_has_overlays = false;
1837
1838 // If this is an S57 quilt, we need to know if there are overlays in it
1839 if (CHART_FAMILY_VECTOR == m_reference_family) {
1840 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1841 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1842 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1843
1844 if (s57chart::IsCellOverlayType(cte.GetFullSystemPath())) {
1845 b_has_overlays = true;
1846 break;
1847 ;
1848 }
1849 }
1850 }
1851
1852 // Using Region logic, and starting from the largest scale chart
1853 // figuratively "draw" charts until the ViewPort window is completely
1854 // quilted over Add only those charts whose scale is smaller than the
1855 // "reference scale"
1856 // const LLRegion cvp_region = vp_local.GetLLRegion(
1857 // wxRect(0, 0, vp_local.pix_width, vp_local.pix_height));
1858 const LLRegion cvp_region = vp_local.GetLLRegion(vp_local.rv_rect);
1859 LLRegion vp_region = cvp_region;
1860 unsigned int ir;
1861
1862 // "Draw" the reference chart first, since it is special in that it
1863 // controls the fine vpscale setting
1864 QuiltCandidate *pqc_ref = NULL;
1865 for (ir = 0; ir < m_pcandidate_array->GetCount();
1866 ir++) // find ref chart entry
1867 {
1868 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1869 if (pqc->dbIndex == m_refchart_dbIndex) {
1870 pqc_ref = pqc;
1871 break;
1872 }
1873 }
1874
1875 // Quilted regions can be simplified to reduce the cost of region operations,
1876 // in this case allow a maximum error of 8 pixels (the rendered display is
1877 // much better, this is only for composing the quilt)
1878 const double z = 111274.96299695622;
1880 double factor = 8.0 / (vp_local.view_scale_ppm * z);
1881
1882 if (pqc_ref) {
1883 const ChartTableEntry &cte_ref =
1884 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1885
1886 LLRegion vpu_region(cvp_region);
1887
1888 // LLRegion chart_region = pqc_ref->GetCandidateRegion();
1889 LLRegion &chart_region = pqc_ref->GetReducedCandidateRegion(factor);
1890
1891 if (cte_ref.GetChartType() != CHART_TYPE_MBTILES) {
1892 if (!chart_region.Empty()) {
1893 vpu_region.Intersect(chart_region);
1894
1895 if (vpu_region.Empty())
1896 pqc_ref->b_include = false; // skip this chart, no true overlap
1897 else {
1898 pqc_ref->b_include = true;
1899 vp_region.Subtract(chart_region); // adding this chart
1900 }
1901 } else
1902 pqc_ref->b_include = false; // skip this chart, empty region
1903 } else {
1904 pqc_ref->b_include = false; // skip this chart, mbtiles
1905 }
1906 }
1907
1908 // Now the rest of the candidates
1909 if (!vp_region.Empty()) {
1910 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1911 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1912
1913 if (pqc->dbIndex == m_refchart_dbIndex) continue; // already did this one
1914
1915 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1916
1917 // Skip overlays on this pass, so that they do not subtract from quilt
1918 // and thus displace a geographical cell with the same extents. Overlays
1919 // will be picked up in the next pass, if any are found
1920 if (CHART_FAMILY_VECTOR == m_reference_family) {
1921 if (s57chart::IsCellOverlayType(cte.GetFullSystemPath())) {
1922 continue;
1923 }
1924 }
1925
1926 // Skip MBTiles
1927 if (CHART_TYPE_MBTILES == cte.GetChartType()) {
1928 pqc->b_include = false; // skip this chart, mbtiles
1929 continue;
1930 }
1931
1932 if (cte.Scale_ge(m_reference_scale)) {
1933 // If this chart appears in the no-show array, then simply include it,
1934 // but don't subtract its region when determining the smaller scale
1935 // charts to include.....
1936 bool b_in_noshow = false;
1937 for (unsigned int ins = 0;
1938 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
1939 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
1940 pqc->dbIndex) // chart is in the noshow list
1941 {
1942 b_in_noshow = true;
1943 break;
1944 }
1945 }
1946
1947 if (!b_in_noshow) {
1948 // Check intersection
1949 LLRegion vpu_region(cvp_region);
1950
1951 // LLRegion chart_region = pqc->GetCandidateRegion( ); //quilt_region;
1952 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
1953
1954 if (!chart_region.Empty()) {
1955 vpu_region.Intersect(chart_region);
1956
1957 if (vpu_region.Empty())
1958 pqc->b_include = false; // skip this chart, no true overlap
1959 else {
1960 pqc->b_include = true;
1961 vp_region.Subtract(chart_region); // adding this chart
1962 }
1963 } else
1964 pqc->b_include = false; // skip this chart, empty region
1965 } else {
1966 pqc->b_include = true;
1967 }
1968
1969 } else {
1970 pqc->b_include = false; // skip this chart, scale is too large
1971 }
1972
1973 if (vp_region.Empty()) // normal stop condition, quilt is full
1974 break;
1975 }
1976 }
1977
1978 // For S57 quilts, walk the list again to identify overlay cells found
1979 // previously, and make sure they are always included and not eclipsed
1980 if (b_has_overlays && (CHART_FAMILY_VECTOR == m_reference_family)) {
1981 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1982 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1983
1984 if (pqc->dbIndex == m_refchart_dbIndex) continue; // already did this one
1985
1986 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1987
1988 if (cte.Scale_ge(m_reference_scale)) {
1989 bool b_in_noshow = false;
1990 for (unsigned int ins = 0;
1991 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
1992 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
1993 pqc->dbIndex) // chart is in the noshow list
1994 {
1995 b_in_noshow = true;
1996 break;
1997 }
1998 }
1999
2000 if (!b_in_noshow) {
2001 // Check intersection
2002 LLRegion vpu_region(cvp_region);
2003
2004 // LLRegion chart_region = pqc->GetCandidateRegion( );
2005 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
2006
2007 if (!chart_region.Empty()) vpu_region.Intersect(chart_region);
2008
2009 if (vpu_region.Empty())
2010 pqc->b_include = false; // skip this chart, no true overlap
2011 else {
2012 bool b_overlay =
2013 s57chart::IsCellOverlayType(cte.GetFullSystemPath());
2014 if (b_overlay) pqc->b_include = true;
2015 }
2016
2017 // If the reference chart happens to be an overlay (e.g.
2018 // 3UABUOYS.000), we dont want it to eclipse any smaller scale
2019 // standard useage charts.
2020 const ChartTableEntry &cte_ref =
2021 ChartData->GetChartTableEntry(m_refchart_dbIndex);
2022 if (s57chart::IsCellOverlayType(cte_ref.GetFullSystemPath())) {
2023 pqc->b_include = true;
2024 }
2025 }
2026 }
2027 }
2028 }
2029
2030 // Walk the candidate list again, marking "eclipsed" charts
2031 // which at this point are the ones with b_include == false .AND. whose
2032 // scale is strictly smaller than the ref scale Also, maintain the member
2033 // list of same
2034
2035 m_eclipsed_stack_array.clear();
2036
2037 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2038 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2039
2040 if (!pqc->b_include) {
2041 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
2042 if (cte.Scale_ge(m_reference_scale) &&
2043 (cte.GetChartType() != CHART_TYPE_MBTILES)) {
2044 m_eclipsed_stack_array.push_back(pqc->dbIndex);
2045 pqc->b_eclipsed = true;
2046 }
2047 }
2048 }
2049
2050 // Potentially add cm93 to the candidate array if the region is not yet
2051 // fully covered
2052 if (((m_bquiltanyproj || m_quilt_proj == PROJECTION_MERCATOR)) &&
2053 !vp_region.Empty()) {
2054 bool b_must_add_cm93 = true;
2055#if 0
2056 // Check the remaining unpainted region.
2057 // It may contain very small "slivers" of empty space, due to mixing of very small scale charts
2058 // with the quilt. If this is the case, do not waste time loading cm93....
2059
2060 OCPNRegionIterator updd( vp_region );
2061 while( updd .HaveRects()) {
2062 wxRect rect = updd.GetRect();
2063 if( ( rect.width > 2 ) && ( rect.height > 2 ) ) {
2064 b_must_add_cm93 = true;
2065 break;
2066 }
2067 updd.NextRect();
2068 }
2069#endif
2070
2071 if (b_must_add_cm93) {
2072 for (int ics = 0; ics < m_parent->GetpCurrentStack()->nEntry; ics++) {
2073 int i = m_parent->GetpCurrentStack()->GetDBIndex(ics);
2074 if (CHART_TYPE_CM93COMP == ChartData->GetDBChartType(i)) {
2075 QuiltCandidate *qcnew = new QuiltCandidate;
2076 qcnew->dbIndex = i;
2077 qcnew->SetScale(ChartData->GetDBChartScale(i));
2078
2079 m_pcandidate_array->Add(qcnew);
2080 }
2081 }
2082 }
2083 }
2084
2085 // Check the list...if no charts are visible due to all being smaller than
2086 // reference_scale, then make sure the smallest scale chart which has any
2087 // true region intersection is visible anyway Also enable any other charts
2088 // which are the same scale as the first one added
2089 bool b_vis = false;
2090 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
2091 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2092 if (pqc->b_include) {
2093 b_vis = true;
2094 break;
2095 }
2096 }
2097
2098 if (!b_vis && m_pcandidate_array->GetCount()) {
2099 int add_scale = 0;
2100
2101 for (int i = m_pcandidate_array->GetCount() - 1; i >= 0; i--) {
2102 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2103 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
2104
2105 // Don't add cm93 yet, it is always covering the quilt...
2106 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
2107
2108 // Don't add MBTiles
2109 if (cte.GetChartType() == CHART_TYPE_MBTILES) continue;
2110
2111 // Check intersection
2112 LLRegion vpck_region(vp_local.GetBBox());
2113
2114 // LLRegion chart_region = pqc->GetCandidateRegion();
2115 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
2116
2117 if (!chart_region.Empty()) vpck_region.Intersect(chart_region);
2118
2119 if (!vpck_region.Empty()) {
2120 if (add_scale) {
2121 if (cte.Scale_eq(add_scale)) {
2122 pqc->b_include = true;
2123 }
2124 } else {
2125 pqc->b_include = true;
2126 add_scale = cte.GetScale();
2127 }
2128 }
2129 }
2130 }
2131
2132 // Finally, build a list of "patches" for the quilt.
2133 // Smallest scale first, as this will be the natural drawing order
2134
2135 m_PatchList.DeleteContents(true);
2136 m_PatchList.Clear();
2137
2138 if (m_pcandidate_array->GetCount()) {
2139 for (int i = m_pcandidate_array->GetCount() - 1; i >= 0; i--) {
2140 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2141
2142 // cm93 add has been deferred until here
2143 // so that it would not displace possible raster or ENCs of larger
2144 // scale
2145 const ChartTableEntry &m = ChartData->GetChartTableEntry(pqc->dbIndex);
2146
2147 if (m.GetChartType() == CHART_TYPE_CM93COMP)
2148 pqc->b_include = true; // force acceptance of this chart in quilt
2149 // would not be in candidate array if not elected
2150
2151 if (pqc->b_include) {
2152 QuiltPatch *pqp = new QuiltPatch;
2153 pqp->dbIndex = pqc->dbIndex;
2154 pqp->ProjType = m.GetChartProjectionType();
2155 // this is the region used for drawing, don't reduce it
2156 // it's visible
2157 pqp->quilt_region = pqc->GetCandidateRegion();
2158 // pqp->quilt_region = pqc->GetReducedCandidateRegion(factor);
2159
2160 pqp->b_Valid = true;
2161
2162 m_PatchList.Append(pqp);
2163 }
2164 }
2165 }
2166 // From here on out, the PatchList is usable...
2167
2168#ifdef QUILT_TYPE_1
2169 if (!m_bquiltanyproj) {
2170 // Establish the quilt projection type
2171 m_quilt_proj = PROJECTION_MERCATOR; // default
2172 ChartBase *ppc = GetLargestScaleChart();
2173 if (ppc) m_quilt_proj = ppc->GetChartProjectionType();
2174 }
2175#endif
2176
2177 if (!m_bquiltanyproj) {
2178 // Walk the PatchList, marking any entries whose projection does not
2179 // match the determined quilt projection
2180 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2181 wxPatchListNode *pcinode = m_PatchList.Item(i);
2182 QuiltPatch *piqp = pcinode->GetData();
2183 if ((piqp->ProjType != m_quilt_proj) &&
2184 (piqp->ProjType != PROJECTION_UNKNOWN))
2185 piqp->b_Valid = false;
2186 }
2187 }
2188
2189 // Walk the PatchList, marking any entries which appear in the noshow array
2190 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2191 wxPatchListNode *pcinode = m_PatchList.Item(i);
2192 QuiltPatch *piqp = pcinode->GetData();
2193 for (unsigned int ins = 0;
2194 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
2195 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
2196 piqp->dbIndex) // chart is in the noshow list
2197 {
2198 piqp->b_Valid = false;
2199 break;
2200 }
2201 }
2202 }
2203
2204 // Generate the final render regions for the patches, one by one
2205
2206 m_covered_region.Clear();
2207#if 1 // this does the same as before with a lot less operations if there are
2208 // many charts
2209
2210 // If the reference chart is cm93, we need to render it first.
2211 bool b_skipCM93 = false;
2212 if (m_reference_type == CHART_TYPE_CM93COMP) {
2213 // find cm93 in the list
2214 for (int i = m_PatchList.GetCount() - 1; i >= 0; i--) {
2215 wxPatchListNode *pcinode = m_PatchList.Item(i);
2216 QuiltPatch *piqp = pcinode->GetData();
2217 if (!piqp->b_Valid) // skip invalid entries
2218 continue;
2219
2220 const ChartTableEntry &m = ChartData->GetChartTableEntry(piqp->dbIndex);
2221
2222 if (m.GetChartType() == CHART_TYPE_CM93COMP) {
2223 // Start with the chart's full region coverage.
2224 piqp->ActiveRegion = piqp->quilt_region;
2225 piqp->ActiveRegion.Intersect(cvp_region);
2226
2227 // Update the next pass full region to remove the region just
2228 // allocated
2229 m_covered_region.Union(piqp->quilt_region);
2230
2231 b_skipCM93 = true; // did this already...
2232 break;
2233 }
2234 }
2235 }
2236
2237 // Proceeding from largest scale to smallest....
2238
2239 for (int i = m_PatchList.GetCount() - 1; i >= 0; i--) {
2240 wxPatchListNode *pcinode = m_PatchList.Item(i);
2241 QuiltPatch *piqp = pcinode->GetData();
2242 if (!piqp->b_Valid) // skip invalid entries
2243 continue;
2244
2245 const ChartTableEntry &cte = ChartData->GetChartTableEntry(piqp->dbIndex);
2246
2247 if (b_skipCM93) {
2248 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
2249 }
2250
2251 // Start with the chart's full region coverage.
2252 piqp->ActiveRegion = piqp->quilt_region;
2253
2254 // this operation becomes expensive with lots of charts
2255 if (!b_has_overlays && m_PatchList.GetCount() < 25)
2256 piqp->ActiveRegion.Subtract(m_covered_region);
2257
2258 piqp->ActiveRegion.Intersect(cvp_region);
2259
2260 // Could happen that a larger scale chart covers completely a smaller
2261 // scale chart
2262 if (piqp->ActiveRegion.Empty() && (piqp->dbIndex != m_refchart_dbIndex))
2263 piqp->b_eclipsed = true;
2264
2265 // Maintain the present full quilt coverage region
2266 piqp->b_overlay = false;
2267 if (cte.GetChartFamily() == CHART_FAMILY_VECTOR) {
2268 piqp->b_overlay = s57chart::IsCellOverlayType(cte.GetFullSystemPath());
2269 }
2270
2271 if (!piqp->b_overlay) m_covered_region.Union(piqp->quilt_region);
2272 }
2273#else
2274 // this is the old algorithm does the same thing in n^2/2 operations instead
2275 // of 2*n-1
2276 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2277 wxPatchListNode *pcinode = m_PatchList.Item(i);
2278 QuiltPatch *piqp = pcinode->GetData();
2279
2280 if (!piqp->b_Valid) // skip invalid entries
2281 continue;
2282
2283 const ChartTableEntry &ctei = ChartData->GetChartTableEntry(piqp->dbIndex);
2284
2285 // Start with the chart's full region coverage.
2286 LLRegion vpr_region = piqp->quilt_region;
2287
2288 // This clause should be moved into the rendering routine for quilts so that
2289 // the actual region logic need only be applied to the render region
2290#if 1 // This clause went away with full-screen quilting
2291 // ...and came back with OpenGL....
2292
2293 // fetch and subtract regions for all larger scale charts
2294 for (unsigned int k = i + 1; k < m_PatchList.GetCount(); k++) {
2295 wxPatchListNode *pnode = m_PatchList.Item(k);
2296 QuiltPatch *pqp = pnode->GetData();
2297
2298 if (!pqp->b_Valid) // skip invalid entries
2299 continue;
2300
2309
2311
2315 // if( ( CHART_TYPE_S57 != ctei.GetChartType() ))
2316 if (!b_has_overlays) {
2317 if (!vpr_region.Empty()) {
2318 const ChartTableEntry &cte =
2319 ChartData->GetChartTableEntry(pqp->dbIndex);
2320 LLRegion larger_scale_chart_region =
2321 pqp->quilt_region; // GetChartQuiltRegion( cte, vp_local );
2322
2323 vpr_region.Subtract(larger_scale_chart_region);
2324 }
2325 }
2326 }
2327#endif
2328
2329 // Whatever is left in the vpr region and has not been yet rendered must
2330 // belong to the current target chart
2331
2332 wxPatchListNode *pinode = m_PatchList.Item(i);
2333 QuiltPatch *pqpi = pinode->GetData();
2334 pqpi->ActiveRegion = vpr_region;
2335
2336 // Move the active region so that upper left is 0,0 in final render
2337 // region
2338 // pqpi->ActiveRegion.Offset( -vp_local.rv_rect.x,
2339 // -vp_local.rv_rect.y );
2340
2341 // Could happen that a larger scale chart covers completely a smaller
2342 // scale chart
2343 if (pqpi->ActiveRegion.Empty()) pqpi->b_eclipsed = true;
2344
2345 // Update the next pass full region to remove the region just allocated
2346 // if( !vpr_region.Empty() )
2347 // unrendered_region.Subtract( vpr_region );
2348
2349 // Maintain the present full quilt coverage region
2350 // if( !pqpi->ActiveRegion.Empty() )
2351 m_covered_region.Union(pqpi->ActiveRegion);
2352 }
2353#endif
2354 // Restore temporary VP Rotation
2355 // vp_local.SetRotationAngle( saved_vp_rotation );
2356
2357 // Walk the list again, removing any entries marked as eclipsed....
2358 unsigned int il = 0;
2359 while (il < m_PatchList.GetCount()) {
2360 wxPatchListNode *pcinode = m_PatchList.Item(il);
2361 QuiltPatch *piqp = pcinode->GetData();
2362 if (piqp->b_eclipsed) {
2363 // Make sure that this chart appears in the eclipsed list...
2364 // This can happen when....
2365 bool b_noadd = false;
2366 for (unsigned int ir = 0; ir < m_eclipsed_stack_array.size(); ir++) {
2367 if (piqp->dbIndex == m_eclipsed_stack_array[ir]) {
2368 b_noadd = true;
2369 break;
2370 }
2371 }
2372 if (!b_noadd) m_eclipsed_stack_array.push_back(piqp->dbIndex);
2373
2374 m_PatchList.DeleteNode(pcinode);
2375 il = 0; // restart the list walk
2376 }
2377
2378 else
2379 il++;
2380 }
2381 // Mark the quilt to indicate need for background clear if the region is
2382 // not fully covered
2383 // m_bneed_clear = !unrendered_region.Empty();
2384 // m_back_region = unrendered_region;
2385
2386 // Finally, iterate thru the quilt and preload all of the required charts.
2387 // For dynamic S57 SENC creation, this is where SENC creation happens
2388 // first.....
2389
2390 // Stop (temporarily) canvas paint events, since some chart loads mught
2391 // Yield(), thus causing performance loss on recursion We will (always??) get
2392 // a refresh on the new Quilt anyway...
2393 m_parent->EnablePaint(false);
2394
2395 // Load and lock all required charts
2396 // First lock required charts already in the cache
2397 // otherwise under memory pressure if chart1 and chart2
2398 // are in the quilt loading chart1 could evict chart2
2399 //
2400 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2401 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2402 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2403 if (!ChartData->IsChartLocked(pqc->dbIndex))
2404 ChartData->LockCacheChart(pqc->dbIndex);
2405 }
2406 }
2407
2408 // Now load and lock any new charts required by the quilt
2409 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2410 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2411 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2412 if (!ChartData->IsChartLocked(pqc->dbIndex)) // Not locked, or not loaded
2413 ChartData->OpenChartFromDBAndLock(pqc->dbIndex, FULL_INIT, true);
2414 }
2415 }
2416
2417#if 0
2418 // first lock charts already in the cache
2419 // otherwise under memory pressure if chart1 and chart2
2420 // are in the quilt loading chart1 could evict chart2
2421 //
2422 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2423 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2424 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2425 if (ChartData->IsChartLocked(pqc->dbIndex)) // already locked
2426 pqc->b_locked = true;
2427 else
2428 pqc->b_locked = ChartData->LockCacheChart(pqc->dbIndex);
2429 }
2430 }
2431
2432 // open charts not in the cache
2433 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2434 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2435 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2436 // I am fairly certain this test can now be removed
2437 // with improved smooth movement logic
2438 // if( !ChartData->IsChartInCache( pqc->dbIndex ) )
2439 // b_stop_movement = true;
2440 // only lock chart if not already locked
2441 if (ChartData->OpenChartFromDBAndLock(pqc->dbIndex, FULL_INIT,
2442 !pqc->b_locked))
2443 pqc->b_locked = true;
2444 }
2445 }
2446#endif
2447
2448 m_parent->EnablePaint(true);
2449 // Build and maintain the array of indexes in this quilt
2450
2451 m_last_index_array = m_index_array; // save the last one for delta checks
2452
2453 m_index_array.clear();
2454
2455 // The index array is to be built in reverse, largest scale first
2456 unsigned int kl = m_PatchList.GetCount();
2457 for (unsigned int k = 0; k < kl; k++) {
2458 wxPatchListNode *cnode = m_PatchList.Item((kl - k) - 1);
2459 m_index_array.push_back(cnode->GetData()->dbIndex);
2460 cnode = cnode->GetNext();
2461 }
2462
2463 // Walk the patch list again, checking the depth units
2464 // If they are all the same, then the value is usable
2465
2466 m_quilt_depth_unit = "";
2467 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
2468 if (pc) {
2469 m_quilt_depth_unit = pc->GetDepthUnits();
2470
2471 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2472 int units = ps52plib->m_nDepthUnitDisplay;
2473 switch (units) {
2474 case 0:
2475 m_quilt_depth_unit = "Feet";
2476 break;
2477 case 1:
2478 m_quilt_depth_unit = "Meters";
2479 break;
2480 case 2:
2481 m_quilt_depth_unit = "Fathoms";
2482 break;
2483 }
2484 }
2485 }
2486
2487 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2488 wxPatchListNode *pnode = m_PatchList.Item(k);
2489 QuiltPatch *pqp = pnode->GetData();
2490
2491 if (!pqp->b_Valid) // skip invalid entries
2492 continue;
2493
2494 ChartBase *pc = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2495 if (pc) {
2496 wxString du = pc->GetDepthUnits();
2497 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2498 int units = ps52plib->m_nDepthUnitDisplay;
2499 switch (units) {
2500 case 0:
2501 du = "Feet";
2502 break;
2503 case 1:
2504 du = "Meters";
2505 break;
2506 case 2:
2507 du = "Fathoms";
2508 break;
2509 }
2510 }
2511 wxString dul = du.Lower();
2512 wxString ml = m_quilt_depth_unit.Lower();
2513
2514 if (dul != ml) {
2515 // Try all the odd cases
2516 if (dul.StartsWith("meters") && ml.StartsWith("meters"))
2517 continue;
2518 else if (dul.StartsWith("metres") && ml.StartsWith("metres"))
2519 continue;
2520 else if (dul.StartsWith("fathoms") && ml.StartsWith("fathoms"))
2521 continue;
2522 else if (dul.StartsWith("met") && ml.StartsWith("met"))
2523 continue;
2524
2525 // They really are different
2526 m_quilt_depth_unit = "";
2527 break;
2528 }
2529 }
2530 }
2531
2532 // And try to prove that all required charts are in the cache
2533 // If one is missing, try to load it
2534 // If still missing, remove its patch from the quilt
2535 // This will probably leave a "black hole" in the quilt...
2536 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2537 wxPatchListNode *pnode = m_PatchList.Item(k);
2538 QuiltPatch *pqp = pnode->GetData();
2539
2540 if (pqp->b_Valid) {
2541 if (!ChartData->IsChartInCache(pqp->dbIndex)) {
2542 wxLogMessage(" Quilt Compose cache miss...");
2543 ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2544 if (!ChartData->IsChartInCache(pqp->dbIndex)) {
2545 wxLogMessage(" Oops, removing from quilt...");
2546 pqp->b_Valid = false;
2547 }
2548 }
2549 }
2550 }
2551
2552 // Make sure the reference chart is in the cache
2553 if (!ChartData->IsChartInCache(m_refchart_dbIndex))
2554 ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
2555
2556 // Walk the patch list again, checking the error factor
2557 // Also, directly mark the patch to indicate if it should be treated as an
2558 // overlay as seen in Austrian Inland series
2559
2560 m_bquilt_has_overlays = false;
2561 m_max_error_factor = 0.;
2562 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2563 wxPatchListNode *pnode = m_PatchList.Item(k);
2564 QuiltPatch *pqp = pnode->GetData();
2565
2566 if (!pqp->b_Valid) // skip invalid entries
2567 continue;
2568
2569 ChartBase *pc = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2570 if (pc) {
2571 m_max_error_factor =
2572 wxMax(m_max_error_factor, pc->GetChart_Error_Factor());
2573 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2574 bool isOverlay = IsChartS57Overlay(pqp->dbIndex);
2575 pqp->b_overlay = isOverlay;
2576 if (isOverlay) m_bquilt_has_overlays = true;
2577 }
2578 }
2579 }
2580
2581 m_bcomposed = true;
2582
2583 m_vp_quilt = vp_in; // save the corresponding ViewPort locally
2584
2585 ChartData->LockCache();
2586
2587 // Create and store a hash value representing the contents of the
2588 // m_extended_stack_array
2589 unsigned long xa_hash = 5381;
2590 for (unsigned int im = 0; im < m_extended_stack_array.size(); im++) {
2591 int dbindex = m_extended_stack_array[im];
2592 xa_hash = ((xa_hash << 5) + xa_hash) + dbindex; /* hash * 33 + dbindex */
2593 }
2594
2595 m_xa_hash = xa_hash;
2596
2597 m_bbusy = false;
2598 return true;
2599}
2600
2601// Compute and update the member quilt render region, considering all scale
2602// factors, group exclusions, etc.
2603void Quilt::ComputeRenderRegion(ViewPort &vp, OCPNRegion &chart_region) {
2604 if (!m_bcomposed) return;
2605
2606 OCPNRegion rendered_region;
2607
2608 if (GetnCharts() && !m_bbusy && !chart_region.Empty()) {
2609 // Walk the quilt, considering each chart from smallest scale to largest
2610
2611 ChartBase *chart = GetFirstChart();
2612
2613 while (chart) {
2614 if (!(chart->GetChartProjectionType() != PROJECTION_MERCATOR &&
2615 vp.b_MercatorProjectionOverride)) {
2616 QuiltPatch *pqp = GetCurrentPatch();
2617 if (pqp->b_Valid) {
2618 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2619 chart_region, pqp->ActiveRegion, chart->GetNativeScale());
2620 if (!get_screen_region.Empty())
2621 rendered_region.Union(get_screen_region);
2622 }
2623 }
2624 chart = GetNextChart();
2625 }
2626 }
2627 // Record the region actually rendered
2628 m_rendered_region = rendered_region;
2629}
2630
2631int g_render;
2632
2633bool Quilt::RenderQuiltRegionViewOnDCNoText(wxMemoryDC &dc, ViewPort &vp,
2634 OCPNRegion &chart_region) {
2635 return DoRenderQuiltRegionViewOnDC(dc, vp, chart_region);
2636}
2637
2638bool Quilt::RenderQuiltRegionViewOnDCTextOnly(wxMemoryDC &dc, ViewPort &vp,
2639 OCPNRegion &chart_region) {
2640 return DoRenderQuiltRegionViewOnDCTextOnly(dc, vp, chart_region);
2641}
2642
2643bool Quilt::DoRenderQuiltRegionViewOnDC(wxMemoryDC &dc, ViewPort &vp,
2644 OCPNRegion &chart_region) {
2645#ifdef ocpnUSE_DIBSECTION
2646 ocpnMemDC tmp_dc;
2647#else
2648 wxMemoryDC tmp_dc;
2649#endif
2650
2651 if (!m_bcomposed) return false;
2652
2653 OCPNRegion rendered_region;
2654
2655 if (GetnCharts() && !m_bbusy) {
2656 OCPNRegion screen_region = chart_region;
2657
2658 // Walk the quilt, drawing each chart from smallest scale to largest
2659 // Render the quilt's charts onto a temp dc
2660 // and blit the active region rectangles to to target dc, one-by-one
2661
2662 ChartBase *chart = GetFirstChart();
2663 int chartsDrawn = 0;
2664
2665 if (!chart_region.Empty()) {
2666 while (chart) {
2667 bool okToRender = true;
2668
2669 if (chart->GetChartProjectionType() != PROJECTION_MERCATOR &&
2670 vp.b_MercatorProjectionOverride)
2671 okToRender = false;
2672
2673 if (!okToRender) {
2674 chart = GetNextChart();
2675 continue;
2676 }
2677 QuiltPatch *pqp = GetCurrentPatch();
2678 if (pqp->b_Valid) {
2679 bool b_chart_rendered = false;
2680 LLRegion get_region = pqp->ActiveRegion;
2681
2682 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2683 chart_region, get_region, chart->GetNativeScale());
2684 if (!get_screen_region.Empty()) {
2685 if (!pqp->b_overlay) {
2686 if (chart->GetChartType() == CHART_TYPE_CM93COMP) {
2687 b_chart_rendered =
2688 chart->RenderRegionViewOnDC(tmp_dc, vp, get_screen_region);
2689 } else {
2690 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2691 if (Chs57) {
2692 if (Chs57->m_RAZBuilt) {
2693 b_chart_rendered = Chs57->RenderRegionViewOnDCNoText(
2694 tmp_dc, vp, get_screen_region);
2695 }
2696 } else {
2697 ChartPlugInWrapper *ChPI =
2698 dynamic_cast<ChartPlugInWrapper *>(chart);
2699 if (ChPI) {
2700 b_chart_rendered = ChPI->RenderRegionViewOnDCNoText(
2701 tmp_dc, vp, get_screen_region);
2702 } else
2703 b_chart_rendered = chart->RenderRegionViewOnDC(
2704 tmp_dc, vp, get_screen_region);
2705
2706 b_chart_rendered = true;
2707 }
2708 }
2709
2710 // if( chart->GetChartType() !=
2711 // CHART_TYPE_CM93COMP )
2712 // b_chart_rendered = true;
2713 screen_region.Subtract(get_screen_region);
2714 }
2715 }
2716
2717 OCPNRegionIterator upd(get_screen_region);
2718 while (upd.HaveRects()) {
2719 wxRect rect = upd.GetRect();
2720 dc.Blit(rect.x, rect.y, rect.width, rect.height, &tmp_dc, rect.x,
2721 rect.y, wxCOPY, true);
2722 upd.NextRect();
2723 }
2724
2725 tmp_dc.SelectObject(wxNullBitmap);
2726
2727 if (b_chart_rendered) rendered_region.Union(get_screen_region);
2728 }
2729
2730 chartsDrawn++;
2731 chart = GetNextChart();
2732 }
2733 }
2734
2735 if (!chartsDrawn) m_parent->GetVP().SetProjectionType(PROJECTION_MERCATOR);
2736
2737 // Render any Overlay patches for s57 charts(cells)
2738 if (m_bquilt_has_overlays && !chart_region.Empty()) {
2739 chart = GetFirstChart();
2740 while (chart) {
2741 QuiltPatch *pqp = GetCurrentPatch();
2742 if (pqp->b_Valid) {
2743 if (pqp->b_overlay) {
2744 LLRegion get_region = pqp->ActiveRegion;
2745 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2746 chart_region, get_region, chart->GetNativeScale());
2747 if (!get_region.Empty()) {
2748 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2749 if (Chs57) {
2750 Chs57->RenderOverlayRegionViewOnDC(tmp_dc, vp,
2751 get_screen_region);
2752 } else {
2753 ChartPlugInWrapper *ChPI =
2754 dynamic_cast<ChartPlugInWrapper *>(chart);
2755 if (ChPI) {
2756 ChPI->RenderRegionViewOnDC(tmp_dc, vp, get_screen_region);
2757 }
2758 }
2759
2760 OCPNRegionIterator upd(get_screen_region);
2761 while (upd.HaveRects()) {
2762 wxRect rect = upd.GetRect();
2763 dc.Blit(rect.x, rect.y, rect.width, rect.height, &tmp_dc,
2764 rect.x, rect.y, wxCOPY, true);
2765 upd.NextRect();
2766 }
2767 tmp_dc.SelectObject(wxNullBitmap);
2768 }
2769 }
2770 }
2771
2772 chart = GetNextChart();
2773 }
2774 }
2775
2776 // Any part of the chart region that was not rendered in the loop needs
2777 // to be cleared
2778 OCPNRegionIterator clrit(screen_region);
2779 while (clrit.HaveRects()) {
2780 wxRect rect = clrit.GetRect();
2781#ifdef __WXOSX__
2782 dc.SetPen(*wxBLACK_PEN);
2783 dc.SetBrush(*wxBLACK_BRUSH);
2784 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height);
2785#else
2786 dc.Blit(rect.x, rect.y, rect.width, rect.height, &dc, rect.x, rect.y,
2787 wxCLEAR);
2788#endif
2789 clrit.NextRect();
2790 }
2791
2792 // Highlighting....
2793 if (m_nHiLiteIndex >= 0) {
2794 OCPNRegion hiregion =
2795 vp.GetVPRegionIntersect(chart_region, GetHiliteRegion(), 1);
2796 wxRect box = hiregion.GetBox();
2797
2798 if (!box.IsEmpty()) {
2799 // Is scratch member bitmap OK?
2800 if (m_pBM) {
2801 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2802 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2803 delete m_pBM;
2804 m_pBM = NULL;
2805 }
2806 }
2807
2808 if (NULL == m_pBM)
2809 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2810
2811 // Copy the entire quilt to my scratch bm
2812 wxMemoryDC q_dc;
2813 q_dc.SelectObject(*m_pBM);
2814 q_dc.Blit(0, 0, vp.rv_rect.width, vp.rv_rect.height, &dc, 0, 0);
2815 q_dc.SelectObject(wxNullBitmap);
2816
2817 // Create a "mask" bitmap from the chart's region
2818 // WxGTK has an error in this method....Creates a color bitmap, not
2819 // usable for mask creation So, I clone with correction
2820 wxBitmap hl_mask_bm(vp.rv_rect.width, vp.rv_rect.height, 1);
2821 wxMemoryDC mdc;
2822 mdc.SelectObject(hl_mask_bm);
2823 mdc.SetBackground(*wxBLACK_BRUSH);
2824 mdc.Clear();
2825 mdc.SetClippingRegion(box);
2826 mdc.SetBackground(*wxWHITE_BRUSH);
2827 mdc.Clear();
2828 mdc.SelectObject(wxNullBitmap);
2829
2830 if (hl_mask_bm.IsOk()) {
2831 wxMask *phl_mask = new wxMask(hl_mask_bm);
2832 m_pBM->SetMask(phl_mask);
2833 q_dc.SelectObject(*m_pBM);
2834
2835 // Create another mask, dc and bitmap for red-out
2836 wxBitmap rbm(vp.rv_rect.width, vp.rv_rect.height);
2837 wxMask *pr_mask = new wxMask(hl_mask_bm);
2838 wxMemoryDC rdc;
2839 rbm.SetMask(pr_mask);
2840 rdc.SelectObject(rbm);
2841 unsigned char hlcolor = 255;
2842 switch (global_color_scheme) {
2843 case GLOBAL_COLOR_SCHEME_DAY:
2844 hlcolor = 255;
2845 break;
2846 case GLOBAL_COLOR_SCHEME_DUSK:
2847 hlcolor = 64;
2848 break;
2849 case GLOBAL_COLOR_SCHEME_NIGHT:
2850 hlcolor = 16;
2851 break;
2852 default:
2853 hlcolor = 255;
2854 break;
2855 }
2856
2857 rdc.SetBackground(wxBrush(wxColour(hlcolor, 0, 0)));
2858 rdc.Clear();
2859
2860 OCPNRegionIterator upd(hiregion);
2861 while (upd.HaveRects()) {
2862 wxRect rect = upd.GetRect();
2863 rdc.Blit(rect.x, rect.y, rect.width, rect.height, &q_dc, rect.x,
2864 rect.y, wxOR, true);
2865 upd.NextRect();
2866 }
2867
2868 OCPNRegionIterator updq(hiregion);
2869 while (updq.HaveRects()) {
2870 wxRect rect = updq.GetRect();
2871 q_dc.Blit(rect.x, rect.y, rect.width, rect.height, &rdc, rect.x,
2872 rect.y, wxCOPY, true);
2873 updq.NextRect();
2874 }
2875
2876 q_dc.SelectObject(wxNullBitmap);
2877 m_pBM->SetMask(NULL);
2878
2879 // Select the scratch BM as the return dc contents
2880 dc.SelectObject(*m_pBM);
2881
2882 // Clear the rdc
2883 rdc.SelectObject(wxNullBitmap);
2884 }
2885 } // box not empty
2886 } // m_nHiLiteIndex
2887
2888 // Fogging....
2889 if (g_fog_overzoom) {
2890 double scale_factor = vp.ref_scale / vp.chart_scale;
2891
2892 if (scale_factor > g_overzoom_emphasis_base) {
2893 float fog = ((scale_factor - g_overzoom_emphasis_base) * 255.) / 20.;
2894 fog = wxMin(fog, 200.); // Don't fog out completely
2895
2896 // Is scratch member bitmap OK?
2897 if (m_pBM) {
2898 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2899 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2900 delete m_pBM;
2901 m_pBM = NULL;
2902 }
2903 }
2904
2905 if (NULL == m_pBM)
2906 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2907
2908 // Copy the entire quilt to my scratch bm
2909 wxMemoryDC q_dc;
2910 q_dc.SelectObject(*m_pBM);
2911 q_dc.Blit(0, 0, vp.rv_rect.width, vp.rv_rect.height, &dc, 0, 0);
2912 q_dc.SelectObject(wxNullBitmap);
2913
2914 wxImage src = m_pBM->ConvertToImage();
2915#if 1
2916 int blur_factor =
2917 wxMin((scale_factor - g_overzoom_emphasis_base) / 4, 4);
2918 if (src.IsOk()) {
2919 wxImage dest = src.Blur(blur_factor);
2920#endif
2921
2922#if 0 // this is fogging effect
2923 unsigned char *bg = src.GetData();
2924 wxColour color = m_parent->GetFogColor();
2925
2926 float transparency = fog;
2927
2928 // destination image
2929 wxImage dest(vp.rv_rect.width, vp.rv_rect.height);
2930 unsigned char *dest_data = (unsigned char *) malloc( vp.rv_rect.width * vp.rv_rect.height * 3 * sizeof(unsigned char) );
2931 unsigned char *d = dest_data;
2932
2933 float alpha = 1.0 - (float)transparency / 255.0;
2934 int sb = vp.rv_rect.width * vp.rv_rect.height;
2935 for( int i = 0; i < sb; i++ ) {
2936 float a = alpha;
2937
2938 int r = ( ( *bg++ ) * a ) + (1.0-a) * color.Red();
2939 *d++ = r;
2940 int g = ( ( *bg++ ) * a ) + (1.0-a) * color.Green();
2941 *d++ = g;
2942 int b = ( ( *bg++ ) * a ) + (1.0-a) * color.Blue();
2943 *d++ = b;
2944 }
2945
2946 dest.SetData( dest_data );
2947#endif
2948
2949 wxBitmap dim(dest);
2950 wxMemoryDC ddc;
2951 ddc.SelectObject(dim);
2952
2953 q_dc.SelectObject(*m_pBM);
2954 OCPNRegionIterator upd(rendered_region);
2955 while (upd.HaveRects()) {
2956 wxRect rect = upd.GetRect();
2957 q_dc.Blit(rect.x, rect.y, rect.width, rect.height, &ddc, rect.x,
2958 rect.y);
2959 upd.NextRect();
2960 }
2961
2962 ddc.SelectObject(wxNullBitmap);
2963 q_dc.SelectObject(wxNullBitmap);
2964
2965 // Select the scratch BM as the return dc contents
2966 dc.SelectObject(*m_pBM);
2967 }
2968 }
2969 } // overzoom
2970
2971 if (!dc.IsOk()) // some error, probably bad charts, to be disabled on next
2972 // compose
2973 {
2974 SubstituteClearDC(dc, vp);
2975 }
2976
2977 } else { // no charts yet, or busy....
2978 SubstituteClearDC(dc, vp);
2979 }
2980
2981 // Record the region actually rendered
2982 m_rendered_region = rendered_region;
2983
2984 m_vp_rendered = vp;
2985 return true;
2986}
2987
2988void Quilt::SubstituteClearDC(wxMemoryDC &dc, ViewPort &vp) {
2989 if (m_pBM) {
2990 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2991 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2992 delete m_pBM;
2993 m_pBM = NULL;
2994 }
2995 }
2996
2997 if (NULL == m_pBM) {
2998 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2999 }
3000
3001 dc.SelectObject(wxNullBitmap);
3002 dc.SelectObject(*m_pBM);
3003 dc.SetBackground(*wxBLACK_BRUSH);
3004 dc.Clear();
3005 m_covered_region.Clear();
3006}
3007
3008bool Quilt::DoRenderQuiltRegionViewOnDCTextOnly(wxMemoryDC &dc, ViewPort &vp,
3009 OCPNRegion &chart_region) {
3010 if (!m_bcomposed) return false;
3011
3012 OCPNRegion rendered_region;
3013
3014 if (GetnCharts() && !m_bbusy) {
3015 OCPNRegion screen_region = chart_region;
3016
3017 // Walk the quilt, drawing each chart from largest scale to smallest
3018
3019 ChartBase *chart = GetLargestScaleChart();
3020
3021 while (chart) {
3022 QuiltPatch *pqp = GetCurrentPatch();
3023 if (pqp->b_Valid) {
3024 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
3025 if (Chs57)
3026 Chs57->RenderRegionViewOnDCTextOnly(dc, vp, chart_region);
3027 else {
3028 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
3029 if (ChPI) {
3030 ChPI->RenderRegionViewOnDCTextOnly(dc, vp, chart_region);
3031 }
3032 }
3033 }
3034
3035 chart = GetNextSmallerScaleChart();
3036 }
3037
3038 } else { // no charts yet, or busy....
3039 SubstituteClearDC(dc, vp);
3040 }
3041
3042 return true;
3043}
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:72
Charts database management
BSB chart management.
Generic Chart canvas base.
Base class for all chart types.
Definition chartbase.h:125
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:151
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:477
Manages the chart database and provides access to chart data.
Definition chartdb.h:94
Wrapper class for plugin-based charts.
Definition chartimg.h:388
An iterator class for OCPNRegion.
Definition OCPNRegion.h:156
A wrapper class for wxRegion with additional functionality.
Definition OCPNRegion.h:56
bool Compose(const ViewPort &vp)
Definition Quilt.cpp:1717
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:84
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:232
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:249
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:242
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:147
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:108
OCPNRegion GetVPRegionIntersect(const OCPNRegion &region, const LLRegion &llregion, int chart_native_scale)
Get the intersection of the viewport with a given region.
Definition viewport.cpp:423
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:227
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:225
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:138
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:247
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:122
Miscellaneous utilities, many of which string related.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:182