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