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 // only charts of the proper projection and type may be quilted....
1355 // Also, only unskewed charts if so directed
1356 // and we avoid adding CM93 Composite until later
1357
1358 // If any PlugIn charts are involved, we make the inclusion test on chart
1359 // family, instead of chart type.
1360 if ((cte.GetChartType() == CHART_TYPE_PLUGIN) ||
1361 (reference_type == CHART_TYPE_PLUGIN)) {
1362 if (reference_family != cte.GetChartFamily()) {
1363 continue;
1364 }
1365 } else {
1366 if (reference_type != cte.GetChartType()) {
1367 // If the chart identifies as a "basemap",
1368 // then include its candidate region.
1369 // The chart is a possible basemap if the chart path name
1370 // contains the string "basemap", not case-sensitive
1371
1372 wxFileName fn(cte.GetFullPath());
1373 if (!fn.GetPath().Lower().Contains("basemap")) continue;
1374 }
1375 }
1376
1377 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
1378
1379 // Also, check the scale of the proposed chart. If too small, skip it.
1380 int candidate_chart_scale = cte.GetScale();
1381 double chart_native_ppm =
1382 m_canvas_scale_factor / (double)candidate_chart_scale;
1383 double zoom_factor = vp_local.view_scale_ppm / chart_native_ppm;
1384 if ((zoom_factor < zoom_test_val) &&
1385 // MBTILES charts report the scale of their smallest layer (i.e. most
1386 // detailed) as native chart scale, even if they are embedding many more
1387 // layers. Since we don't know their maximum scale at this stage, we
1388 // don't skip the chart if this native scale is apparently too small.
1389 (cte.GetChartType() != CHART_TYPE_MBTILES)) {
1390 m_extended_stack_array.pop_back();
1391 continue;
1392 }
1393
1394 double skew_norm = cte.GetChartSkew();
1395 if (skew_norm > 180.) skew_norm -= 360.;
1396
1397 if ((m_bquiltskew ? 1 : fabs(skew_norm) < 1.0) &&
1398 (m_bquiltanyproj || cte.GetChartProjectionType() == quilt_proj)) {
1399 QuiltCandidate *qcnew = new QuiltCandidate;
1400 qcnew->dbIndex = istack;
1401 qcnew->SetScale(cte.GetScale());
1402 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1403 }
1404
1405 // if( ( reference_type == cte.GetChartType() ) ||
1406 // ( (cte.GetChartType() == CHART_TYPE_PLUGIN ) &&
1407 // (reference_family == cte.GetChartFamily() )) || (
1408 // (reference_type == CHART_TYPE_PLUGIN ) &&
1409 // (reference_family == cte.GetChartFamily() )) ){
1410 //
1411 // if( ( m_bquiltskew ? 1: fabs( skew_norm ) < 1.0 )
1412 // && ( m_bquiltanyproj || cte.GetChartProjectionType()
1413 // == quilt_proj )
1414 // && ( cte.GetChartType() != CHART_TYPE_CM93COMP ) ) {
1415 // QuiltCandidate *qcnew = new QuiltCandidate;
1416 // qcnew->dbIndex = i;
1417 // qcnew->ChartScale = cte.GetScale();
1418 //
1419 // m_pcandidate_array->push_back( qcnew ); //
1420 // auto-sorted on scale
1421 // }
1422 // }
1423 }
1424
1425 // Search the entire database, potentially adding all charts
1426 // which intersect the ViewPort in any way
1427 // .AND. other requirements.
1428 // Again, skipping cm93 for now
1429 int n_all_charts = ChartData->GetChartTableEntries();
1430
1431 LLBBox viewbox = vp_local.GetBBox();
1432 int sure_index = -1;
1433 int sure_index_scale = 0;
1434 int sure_index_type = -1;
1435
1436 for (int i = 0; i < n_all_charts; i++) {
1437 // We can eliminate some charts immediately
1438 // Try to make these tests in some sensible order....
1439
1440 int groupIndex = m_parent->m_groupIndex;
1441 if ((groupIndex > 0) && (!ChartData->IsChartInGroup(i, groupIndex)))
1442 continue;
1443
1444 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1445
1446 // Skip any charts in Exclude array
1447 if (ChartData->IsChartDirectoryExcluded(cte.GetFullPath())) continue;
1448
1449 if (cte.GetChartType() == CHART_TYPE_CM93COMP)
1450 m_fullscreen_index_array.push_back(i);
1451
1452 // On android, SDK > 29, we require that the directory of charts be
1453 // "writable" as determined by Android Java file system
1454#ifdef __ANDROID__
1455 wxFileName fn(cte.GetFullSystemPath());
1456 if (!androidIsDirWritable(fn.GetPath())) continue;
1457#endif
1458 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
1459
1460 const LLBBox &chart_box = cte.GetBBox();
1461 if ((viewbox.IntersectOut(chart_box))) continue;
1462
1463 if (cte.GetChartType() == CHART_TYPE_PLUGIN) {
1464 if (!ChartData->IsChartAvailable(i)) continue;
1465 }
1466
1467 m_fullscreen_index_array.push_back(i);
1468
1469 // If the chart identifies as a "basemap",
1470 // then include its candidate region.
1471 // The chart is a possible basemap if the chart path name
1472 // contains the string "basemap", not case-sensitive
1473
1474 bool guest_family_include = false;
1475 if (reference_family != cte.GetChartFamily()) {
1476 wxFileName fn(cte.GetFullPath());
1477 if (fn.GetPath().Lower().Contains("basemap")) {
1478 guest_family_include = true;
1479 }
1480
1481 if (m_lost_refchart_dbIndex == i) guest_family_include = true;
1482
1483 if ((cte.GetChartType() != CHART_TYPE_MBTILES) && !guest_family_include)
1484 continue;
1485 }
1486
1487 if (!m_bquiltanyproj && quilt_proj != cte.GetChartProjectionType())
1488 continue;
1489
1490 double skew_norm = cte.GetChartSkew();
1491 if (skew_norm > 180.) skew_norm -= 360.;
1492
1493 if (!m_bquiltskew && fabs(skew_norm) > 1.0) continue;
1494
1495 // Special case for S57 ENC
1496 // Add the chart only if the chart's fractional area exceeds n%
1497#if 0
1498 if( CHART_TYPE_S57 == cte.GetChartType() ) {
1499 //Get the fractional area of this candidate
1500 double chart_area = (cte.GetLonMax() - cte.GetLonMin()) *
1501 (cte.GetLatMax() - cte.GetLatMin());
1502 double quilt_area = viewbox.GetLonRange() * viewbox.GetLatRange();
1503 if ((chart_area / quilt_area) < .01) continue;
1504 }
1505#endif
1506 int candidate_chart_scale = cte.GetScale();
1507
1508 // Try to guarantee that there is one chart added with scale larger than
1509 // reference scale
1510 // Take note here, and keep track of the smallest scale chart that is
1511 // larger scale than reference....
1512 if (!cte.Scale_ge(reference_scale)) {
1513 if (cte.Scale_gt(sure_index_scale)) {
1514 sure_index = i;
1515 sure_index_scale = candidate_chart_scale;
1516 sure_index_type = cte.GetChartType();
1517 }
1518 }
1519
1520 // At this point, the candidate is the right type, skew, and projection,
1521 // and is on-screen somewhere.... Now add the candidate if its scale is
1522 // smaller than the reference scale, or is not excessively underzoomed.
1523
1524 // Calculate zoom factor for this chart
1525 double chart_native_ppm =
1526 m_canvas_scale_factor / (double)candidate_chart_scale;
1527 double zoom_factor = vp_in.view_scale_ppm / chart_native_ppm;
1528 double zoom_factor_test_extra = 0.2;
1529
1530 // For some quilts (e.g.cm93 + MBTiles), the reference scale is not known
1531 // or is default 1e8 value. This would exclude large scale MBtiles.
1532 // Adjust the reference scale test value so as to include the MBTiles,
1533 // if their zoom factor is suitable
1534 double ref_scale_test = reference_scale;
1535 if (cte.GetChartType() == CHART_TYPE_MBTILES)
1536 ref_scale_test = candidate_chart_scale;
1537
1538 if ((cte.Scale_ge(ref_scale_test) && (zoom_factor > zoom_test_val)) ||
1539 (zoom_factor > zoom_factor_test_extra)) {
1540 LLRegion cell_region = GetChartQuiltRegion(cte, vp_local);
1541
1542 // this is false if the chart has no actual overlap on screen
1543 // or lots of NoCovr regions. US3EC04.000 is a good example
1544 // i.e the full bboxes overlap, but the actual vp intersect is null.
1545 if (!cell_region.Empty()) {
1546 // Check to see if this chart is already in the stack array
1547 // by virtue of being under the Viewport center point....
1548 bool b_exists = false;
1549 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1550 if (i == m_extended_stack_array[ir]) {
1551 b_exists = true;
1552 break;
1553 }
1554 }
1555
1556 if (!b_exists) {
1557 // Check to be sure that this chart has not already been added
1558 // i.e. charts that have exactly the same file name and nearly the
1559 // same mod time These charts can be in the database due to having
1560 // the exact same chart in different directories, as may be desired
1561 // for some grouping schemes
1562 // Extended to also check for "identical" charts, having exact same
1563 // EditionDate
1564 bool b_noadd = false;
1565 auto &cte_n = ChartData->GetChartTableEntry(i);
1566 for (unsigned int id = 0; id < m_extended_stack_array.size(); id++) {
1567 if (m_extended_stack_array[id] != -1) {
1568 auto &cte_m =
1569 ChartData->GetChartTableEntry(m_extended_stack_array[id]);
1570 bool bsameTime = false;
1571 if (cte_m.GetFileTime() && cte_n.GetFileTime()) {
1572 if (labs(cte_m.GetFileTime() - cte_n.GetFileTime()) < 60)
1573 bsameTime = true;
1574 }
1575 if (cte_m.GetChartEditionDate() == cte_n.GetChartEditionDate())
1576 bsameTime = true;
1577
1578 if (bsameTime) {
1579 if (cte_n.GetpFileName()->IsSameAs(*(cte_m.GetpFileName())))
1580 b_noadd = true;
1581 }
1582 }
1583 }
1584
1585 if (!b_noadd) {
1586 m_extended_stack_array.push_back(i);
1587
1588 QuiltCandidate *qcnew = new QuiltCandidate;
1589 qcnew->dbIndex = i;
1590 qcnew->SetScale(
1591 candidate_chart_scale); // ChartData->GetDBChartScale( i );
1592
1593 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1594
1595 b_need_resort = true;
1596 }
1597 }
1598 }
1599 }
1600 } // for all charts
1601
1602 // Check to be sure that at least one chart was added that is larger scale
1603 // than reference scale
1604 if (-1 != sure_index) {
1605 // check to see if it is already in
1606 bool sure_exists = false;
1607 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1608 if (sure_index == m_extended_stack_array[ir]) {
1609 sure_exists = true;
1610 break;
1611 }
1612 }
1613
1614 // If not already added, do so now
1615 if (!sure_exists && (sure_index_type != CHART_TYPE_MBTILES)) {
1616 m_extended_stack_array.push_back(sure_index);
1617
1618 QuiltCandidate *qcnew = new QuiltCandidate;
1619 qcnew->dbIndex = sure_index;
1620 qcnew->SetScale(ChartData->GetDBChartScale(sure_index));
1621 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1622
1623 b_need_resort = true;
1624 }
1625 }
1626
1627 // Re sort the extended stack array on scale
1628 if (b_need_resort && m_extended_stack_array.size() > 1) {
1629 std::sort(m_extended_stack_array.begin(), m_extended_stack_array.end(),
1630 CompareScalesStd);
1631 }
1632 // Sort the full screen array too
1633 std::sort(m_fullscreen_index_array.begin(), m_fullscreen_index_array.end(),
1634 CompareScalesStdFULLSCREEN);
1635
1636 return true;
1637}
1638
1639int Quilt::AdjustRefSelection(const ViewPort &vp_in) {
1640 // Starting from the currently selected Ref chart,
1641 // choose a ref chart that meets the required under/overzoom limits
1642 // It might be the same, so no change required
1643
1644 if (!ChartData) return false;
1645
1646 if (ChartData
1647 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1648 return false;
1649
1650 ViewPort vp_local = vp_in; // need a non-const copy
1651
1652 // As ChartdB data is always in rectilinear space, region calculations need
1653 // to be done with no VP rotation
1654 vp_local.SetRotationAngle(0.);
1655
1656 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1657
1658 ChartFamilyEnum family = CHART_FAMILY_RASTER;
1659 ChartTypeEnum type = CHART_TYPE_KAP;
1660
1661 // Get the desired family/type
1662 if (m_refchart_dbIndex >= 0) {
1663 const ChartTableEntry &cte_ref =
1664 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1665 type = (ChartTypeEnum)cte_ref.GetChartType();
1666 family = (ChartFamilyEnum)cte_ref.GetChartFamily();
1667 }
1668
1669 int ret_index = AdjustRefOnZoom(true, family, type, vp_in.chart_scale);
1670
1671 return ret_index;
1672}
1673
1674double Quilt::GetBestStartScale(int dbi_ref_hint, const ViewPort &vp_in) {
1675 if (!ChartData) return vp_in.view_scale_ppm;
1676
1677 if (ChartData
1678 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1679 return vp_in.view_scale_ppm;
1680
1681 ViewPort vp_local = vp_in; // need a non-const copy
1682
1683 // Validate Reference Chart hint
1684 int tentative_ref_index = dbi_ref_hint;
1685 if (dbi_ref_hint < 0) {
1686 // arbitrarily select reference chart as largest scale on current stack
1687 // if( !pCurrentStack ) {
1688 // pCurrentStack = new ChartStack;
1689 // ChartData->BuildChartStack( pCurrentStack, vp_local.clat,
1690 // vp_local.clon );
1691 // }
1692 tentative_ref_index = m_parent->GetpCurrentStack()->GetDBIndex(0);
1693 }
1694
1695 // As ChartdB data is always in rectilinear space, region calculations need
1696 // to be done with no VP rotation
1697 // double saved_vp_rotation = vp_local.rotation; // save
1698 // a copy
1699 vp_local.SetRotationAngle(0.);
1700
1701 BuildExtendedChartStackAndCandidateArray(tentative_ref_index, vp_local);
1702
1703 // tentative choice might not be in the extended stack....
1704 bool bf = false;
1705 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1706 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1707 if (qc->dbIndex == tentative_ref_index) {
1708 bf = true;
1709 break;
1710 }
1711 }
1712
1713 if (!bf && m_pcandidate_array->GetCount()) {
1714 tentative_ref_index = GetNewRefChart();
1715 BuildExtendedChartStackAndCandidateArray(tentative_ref_index, vp_local);
1716 }
1717
1718 double proposed_scale_onscreen = vp_in.chart_scale;
1719
1720 if (m_pcandidate_array->GetCount()) {
1721 SetReferenceChart(tentative_ref_index);
1722 } else {
1723 // Need to choose some chart, find a quiltable candidate
1724 bool bfq = false;
1725 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1726 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1727 if (IsChartQuiltableRef(qc->dbIndex)) {
1728 SetReferenceChart(qc->dbIndex);
1729 bfq = true;
1730 break;
1731 }
1732 }
1733
1734 if (!bfq) // fallback to first chart in stack
1735 SetReferenceChart(m_parent->GetpCurrentStack()->GetDBIndex(0));
1736 }
1737
1738 if (m_refchart_dbIndex >= 0) {
1739 // Suggest a scale so that the largest scale candidate is "nominally"
1740 // scaled, meaning not overzoomed, and not underzoomed
1741 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
1742 if (pc) {
1743 double min_ref_scale =
1744 pc->GetNormalScaleMin(m_parent->GetCanvasScaleFactor(), false);
1745 double max_ref_scale = pc->GetNormalScaleMax(
1746 m_parent->GetCanvasScaleFactor(), m_canvas_width);
1747 if ((proposed_scale_onscreen >= min_ref_scale) &&
1748 (proposed_scale_onscreen <= max_ref_scale))
1749 return vp_in.view_scale_ppm;
1750 else {
1751 proposed_scale_onscreen = wxMin(proposed_scale_onscreen, max_ref_scale);
1752 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, min_ref_scale);
1753 }
1754 }
1755 }
1756 return m_parent->GetCanvasScaleFactor() / proposed_scale_onscreen;
1757}
1758
1759ChartBase *Quilt::GetRefChart() {
1760 if (m_refchart_dbIndex >= 0 && ChartData)
1761 return ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
1762 return nullptr;
1763}
1764
1765void Quilt::UnlockQuilt() {
1766 wxASSERT(m_bbusy == false);
1767 ChartData->UnLockCache();
1768 // unlocked only charts owned by the Quilt
1769 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1770 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1771 ChartData->UnLockCacheChart(pqc->dbIndex);
1772 }
1773}
1774
1775bool Quilt::Compose(const ViewPort &vp_in) {
1776 if (!ChartData) return false;
1777 if (ChartData
1778 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1779 return false;
1780
1781 if (!m_parent->GetpCurrentStack()) return false;
1782
1783 if (m_bbusy) return false;
1784
1785 // XXX call before setting m_bbusy for wxASSERT in UnlockQuilt
1786 UnlockQuilt();
1787 m_bbusy = true;
1788
1789 ViewPort vp_local = vp_in; // need a non-const copy
1790
1791 // Get Reference Chart parameters
1792 if (m_refchart_dbIndex >= 0) {
1793 const ChartTableEntry &cte_ref =
1794 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1795 m_reference_scale = cte_ref.GetScale();
1796 m_reference_type = cte_ref.GetChartType();
1797 if (!m_bquiltanyproj)
1798 m_quilt_proj = ChartData->GetDBChartProj(m_refchart_dbIndex);
1799 m_reference_family = cte_ref.GetChartFamily();
1800 }
1801
1802 // Set up the viewport projection type
1803 if (!m_bquiltanyproj) vp_local.SetProjectionType(m_quilt_proj);
1804
1805 // As ChartdB data is always in rectilinear space, region calculations need
1806 // to be done with no VP rotation
1807 // double saved_vp_rotation = vp_local.rotation; //
1808 // save a copy vp_local.SetRotationAngle( 0. );
1809
1810 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1811
1812 // It can happen (in groups switch, or single->quilt mode) that there
1813 // is no refchart known, but there are charts available in the piano.
1814 // Detect this case, and build the quilt based on the smallest scale chart
1815 // anywhere on screen.
1816
1817 // if ((m_refchart_dbIndex < 0) && m_extended_stack_array.size()){
1818 // // Take the smallest scale chart in the array.
1819 // int tentative_dbIndex = m_extended_stack_array.back();
1820 //
1821 // // Verify that the zoom scale is acceptable.
1822 // const ChartTableEntry &cte =
1823 // ChartData->GetChartTableEntry(tentative_dbIndex); int
1824 // candidate_chart_scale = cte.GetScale(); double chart_native_ppm =
1825 // m_canvas_scale_factor / (double)candidate_chart_scale;
1826 // double zoom_factor = vp_local.view_scale_ppm / chart_native_ppm;
1827 // if (zoom_factor > 0.1){
1828 // m_refchart_dbIndex = tentative_dbIndex;
1829 // BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex,
1830 // vp_local);
1831 // }
1832 // }
1833
1834 // It is possible that the reference chart is not really part of the
1835 // visible quilt This can happen when the reference chart is panned
1836 // off-screen in full screen quilt mode
1837 // If this situation occurs, we need to immediately select a new reference
1838 // chart And rebuild the Candidate Array
1839 //
1840 // We also save the dbIndex of the "lost" chart, and try to recover it
1841 // on subsequent quilts, typically as the user pans the "lost" chart back
1842 // on-screen. The "lost" chart logic is reset on any zoom operations. See
1843 // FS#1221
1844 //
1845 // A special case occurs with cm93 composite chart set as the reference
1846 // chart: It is not at this point a candidate, so won't be found by the
1847 // search This case is indicated if the candidate count is zero. If so, do
1848 // not invalidate the ref chart
1849 bool bf = false;
1850 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1851 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1852 if (qc->dbIndex == m_refchart_dbIndex) {
1853 bf = true;
1854 break;
1855 }
1856 }
1857
1858 // Process "lost chart" logic by default
1859 // However, if the chart family has been directly set
1860 // by SelectRefChartByFamily(), then honor the selection always.
1861 if (m_chart_familyFix == CHART_FAMILY_UNKNOWN) {
1862 if (!bf && m_pcandidate_array->GetCount() &&
1863 (m_reference_type != CHART_TYPE_CM93COMP)) {
1864 m_lost_refchart_dbIndex = m_refchart_dbIndex; // save for later
1865 int candidate_ref_index = GetNewRefChart();
1866 if (m_refchart_dbIndex != candidate_ref_index) {
1867 m_refchart_dbIndex = candidate_ref_index;
1868 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1869 }
1870 // There was no viable candidate of smaller scale than the "lost
1871 // chart", so choose the smallest scale chart in the candidate list.
1872 // Exception: if the smallest scale chart in the candidate list
1873 // is a different family, then do not change the reference chart
1874 else {
1875 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1876 if (m_pcandidate_array->GetCount()) {
1877 int alternate_index =
1878 m_pcandidate_array->Item(m_pcandidate_array->GetCount() - 1)
1879 ->dbIndex;
1880 const ChartTableEntry &cte_alt =
1881 ChartData->GetChartTableEntry(alternate_index);
1882
1883 if (GetRefFamily() == cte_alt.GetChartFamily()) {
1884 m_refchart_dbIndex = alternate_index;
1885 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex,
1886 vp_local);
1887 }
1888 }
1889 }
1890 }
1891 }
1892
1893 if ((-1 != m_lost_refchart_dbIndex) &&
1894 (m_lost_refchart_dbIndex != m_refchart_dbIndex)) {
1895 // Is the lost chart in the extended stack ?
1896 // If so, build a new Cnadidate array based upon the lost chart
1897 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1898 if (m_lost_refchart_dbIndex == m_extended_stack_array[ir]) {
1899 m_refchart_dbIndex = m_lost_refchart_dbIndex;
1900 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1901 m_lost_refchart_dbIndex = -1;
1902 break;
1903 }
1904 }
1905 }
1906
1907 bool b_has_overlays = false;
1908
1909 // If this is an S57 quilt, we need to know if there are overlays in it
1910 if (CHART_FAMILY_VECTOR == m_reference_family) {
1911 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1912 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1913 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1914
1915 if (s57chart::IsCellOverlayType(cte.GetFullSystemPath())) {
1916 b_has_overlays = true;
1917 break;
1918 }
1919 }
1920 }
1921
1922 // Using Region logic, and starting from the largest scale chart
1923 // figuratively "draw" charts until the ViewPort window is completely
1924 // quilted over Add only those charts whose scale is smaller than the
1925 // "reference scale"
1926 // const LLRegion cvp_region = vp_local.GetLLRegion(
1927 // wxRect(0, 0, vp_local.pix_width, vp_local.pix_height));
1928 const LLRegion cvp_region = vp_local.GetLLRegion(vp_local.rv_rect);
1929 LLRegion vp_region = cvp_region;
1930 unsigned int ir;
1931
1932 // "Draw" the reference chart first, since it is special in that it
1933 // controls the fine vpscale setting
1934 QuiltCandidate *pqc_ref = NULL;
1935 for (ir = 0; ir < m_pcandidate_array->GetCount();
1936 ir++) // find ref chart entry
1937 {
1938 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1939 if (pqc->dbIndex == m_refchart_dbIndex) {
1940 pqc_ref = pqc;
1941 break;
1942 }
1943 }
1944
1945 // Quilted regions can be simplified to reduce the cost of region operations,
1946 // in this case allow a maximum error of 8 pixels (the rendered display is
1947 // much better, this is only for composing the quilt)
1948 const double z = 111274.96299695622;
1950 double factor = 8.0 / (vp_local.view_scale_ppm * z);
1951
1952 if (pqc_ref) {
1953 const ChartTableEntry &cte_ref =
1954 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1955
1956 LLRegion vpu_region(cvp_region);
1957
1958 // LLRegion chart_region = pqc_ref->GetCandidateRegion();
1959 LLRegion &chart_region = pqc_ref->GetReducedCandidateRegion(factor);
1960
1961 if (cte_ref.GetChartType() != CHART_TYPE_MBTILES) {
1962 if (!chart_region.Empty()) {
1963 vpu_region.Intersect(chart_region);
1964
1965 if (vpu_region.Empty())
1966 pqc_ref->b_include = false; // skip this chart, no true overlap
1967 else {
1968 pqc_ref->b_include = true;
1969 vp_region.Subtract(chart_region); // adding this chart
1970 }
1971 } else
1972 pqc_ref->b_include = false; // skip this chart, empty region
1973 } else {
1974 pqc_ref->b_include = false; // skip this chart, mbtiles
1975 }
1976 }
1977
1978 // Now the rest of the candidates
1979 if (!vp_region.Empty()) {
1980 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1981 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1982
1983 if (pqc->dbIndex == m_refchart_dbIndex) continue; // already did this one
1984
1985 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1986
1987 // Skip overlays on this pass, so that they do not subtract from quilt
1988 // and thus displace a geographical cell with the same extents. Overlays
1989 // will be picked up in the next pass, if any are found
1990 if (CHART_FAMILY_VECTOR == m_reference_family) {
1991 if (s57chart::IsCellOverlayType(cte.GetFullSystemPath())) {
1992 continue;
1993 }
1994 }
1995
1996 // Skip MBTiles
1997 if (CHART_TYPE_MBTILES == cte.GetChartType()) {
1998 pqc->b_include = false; // skip this chart, mbtiles
1999 continue;
2000 }
2001
2002 if (cte.Scale_ge(m_reference_scale)) {
2003 // If this chart appears in the no-show array, then simply include it,
2004 // but don't subtract its region when determining the smaller scale
2005 // charts to include.....
2006 bool b_in_noshow = false;
2007 for (unsigned int ins = 0;
2008 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
2009 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
2010 pqc->dbIndex) // chart is in the noshow list
2011 {
2012 b_in_noshow = true;
2013 break;
2014 }
2015 }
2016
2017 if (!b_in_noshow) {
2018 // Check intersection
2019 LLRegion vpu_region(cvp_region);
2020
2021 // LLRegion chart_region = pqc->GetCandidateRegion( ); //quilt_region;
2022 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
2023
2024 if (!chart_region.Empty()) {
2025 vpu_region.Intersect(chart_region);
2026
2027 if (vpu_region.Empty())
2028 pqc->b_include = false; // skip this chart, no true overlap
2029 else {
2030 pqc->b_include = true;
2031 vp_region.Subtract(chart_region); // adding this chart
2032 }
2033 } else
2034 pqc->b_include = false; // skip this chart, empty region
2035 } else {
2036 pqc->b_include = true;
2037 }
2038
2039 } else {
2040 pqc->b_include = false; // skip this chart, scale is too large
2041 }
2042
2043 if (vp_region.Empty()) // normal stop condition, quilt is full
2044 break;
2045 }
2046 }
2047
2048 // For S57 quilts, walk the list again to identify overlay cells found
2049 // previously, and make sure they are always included and not eclipsed
2050 if (b_has_overlays && (CHART_FAMILY_VECTOR == m_reference_family)) {
2051 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2052 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2053
2054 if (pqc->dbIndex == m_refchart_dbIndex) continue; // already did this one
2055
2056 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
2057
2058 if (cte.Scale_ge(m_reference_scale)) {
2059 bool b_in_noshow = false;
2060 for (unsigned int ins = 0;
2061 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
2062 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
2063 pqc->dbIndex) // chart is in the noshow list
2064 {
2065 b_in_noshow = true;
2066 break;
2067 }
2068 }
2069
2070 if (!b_in_noshow) {
2071 // Check intersection
2072 LLRegion vpu_region(cvp_region);
2073
2074 // LLRegion chart_region = pqc->GetCandidateRegion( );
2075 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
2076
2077 if (!chart_region.Empty()) vpu_region.Intersect(chart_region);
2078
2079 if (vpu_region.Empty())
2080 pqc->b_include = false; // skip this chart, no true overlap
2081 else {
2082 bool b_overlay =
2083 s57chart::IsCellOverlayType(cte.GetFullSystemPath());
2084 if (b_overlay) pqc->b_include = true;
2085 }
2086
2087 // If the reference chart happens to be an overlay (e.g.
2088 // 3UABUOYS.000), we dont want it to eclipse any smaller scale
2089 // standard useage charts.
2090 const ChartTableEntry &cte_ref =
2091 ChartData->GetChartTableEntry(m_refchart_dbIndex);
2092 if (s57chart::IsCellOverlayType(cte_ref.GetFullSystemPath())) {
2093 pqc->b_include = true;
2094 }
2095 }
2096 }
2097 }
2098 }
2099
2100 // Walk the candidate list again, marking "eclipsed" charts
2101 // which at this point are the ones with b_include == false .AND. whose
2102 // scale is strictly smaller than the ref scale Also, maintain the member
2103 // list of same
2104
2105 m_eclipsed_stack_array.clear();
2106
2107 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2108 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2109
2110 if (!pqc->b_include) {
2111 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
2112 if (cte.Scale_ge(m_reference_scale) &&
2113 (cte.GetChartType() != CHART_TYPE_MBTILES)) {
2114 m_eclipsed_stack_array.push_back(pqc->dbIndex);
2115 pqc->b_eclipsed = true;
2116 }
2117 }
2118 }
2119
2120 // Potentially add cm93 to the candidate array if the region is not yet
2121 // fully covered
2122 if (((m_bquiltanyproj || m_quilt_proj == PROJECTION_MERCATOR)) &&
2123 !vp_region.Empty()) {
2124 bool b_must_add_cm93 = true;
2125#if 0
2126 // Check the remaining unpainted region.
2127 // It may contain very small "slivers" of empty space, due to mixing of very small scale charts
2128 // with the quilt. If this is the case, do not waste time loading cm93....
2129
2130 OCPNRegionIterator updd( vp_region );
2131 while( updd .HaveRects()) {
2132 wxRect rect = updd.GetRect();
2133 if( ( rect.width > 2 ) && ( rect.height > 2 ) ) {
2134 b_must_add_cm93 = true;
2135 break;
2136 }
2137 updd.NextRect();
2138 }
2139#endif
2140
2141 if (b_must_add_cm93) {
2142 for (int ics = 0; ics < m_parent->GetpCurrentStack()->nEntry; ics++) {
2143 int i = m_parent->GetpCurrentStack()->GetDBIndex(ics);
2144 if (CHART_TYPE_CM93COMP == ChartData->GetDBChartType(i)) {
2145 QuiltCandidate *qcnew = new QuiltCandidate;
2146 qcnew->dbIndex = i;
2147 qcnew->SetScale(ChartData->GetDBChartScale(i));
2148
2149 m_pcandidate_array->Add(qcnew);
2150 }
2151 }
2152 }
2153 }
2154
2155 // Check the list...if no charts are visible due to all being smaller than
2156 // reference_scale, then make sure the smallest scale chart which has any
2157 // true region intersection is visible anyway Also enable any other charts
2158 // which are the same scale as the first one added
2159 bool b_vis = false;
2160 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
2161 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2162 if (pqc->b_include) {
2163 b_vis = true;
2164 break;
2165 }
2166 }
2167
2168 if (!b_vis && m_pcandidate_array->GetCount()) {
2169 int add_scale = 0;
2170
2171 for (int i = m_pcandidate_array->GetCount() - 1; i >= 0; i--) {
2172 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2173 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
2174
2175 // Don't add cm93 yet, it is always covering the quilt...
2176 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
2177
2178 // Don't add MBTiles
2179 if (cte.GetChartType() == CHART_TYPE_MBTILES) continue;
2180
2181 // Check intersection
2182 LLRegion vpck_region(vp_local.GetBBox());
2183
2184 // LLRegion chart_region = pqc->GetCandidateRegion();
2185 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
2186
2187 if (!chart_region.Empty()) vpck_region.Intersect(chart_region);
2188
2189 if (!vpck_region.Empty()) {
2190 if (add_scale) {
2191 if (cte.Scale_eq(add_scale)) {
2192 pqc->b_include = true;
2193 }
2194 } else {
2195 pqc->b_include = true;
2196 add_scale = cte.GetScale();
2197 }
2198 }
2199 }
2200 }
2201
2202 // Finally, build a list of "patches" for the quilt.
2203 // Smallest scale first, as this will be the natural drawing order
2204
2205 m_PatchList.DeleteContents(true);
2206 m_PatchList.Clear();
2207
2208 if (m_pcandidate_array->GetCount()) {
2209 for (int i = m_pcandidate_array->GetCount() - 1; i >= 0; i--) {
2210 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2211
2212 // cm93 add has been deferred until here
2213 // so that it would not displace possible raster or ENCs of larger
2214 // scale
2215 const ChartTableEntry &m = ChartData->GetChartTableEntry(pqc->dbIndex);
2216
2217 if (m.GetChartType() == CHART_TYPE_CM93COMP)
2218 pqc->b_include = true; // force acceptance of this chart in quilt
2219 // would not be in candidate array if not elected
2220
2221 if (pqc->b_include) {
2222 QuiltPatch *pqp = new QuiltPatch;
2223 pqp->dbIndex = pqc->dbIndex;
2224 pqp->ProjType = m.GetChartProjectionType();
2225 // this is the region used for drawing, don't reduce it
2226 // it's visible
2227 pqp->quilt_region = pqc->GetCandidateRegion();
2228 // pqp->quilt_region = pqc->GetReducedCandidateRegion(factor);
2229
2230 pqp->b_Valid = true;
2231
2232 m_PatchList.Append(pqp);
2233 }
2234 }
2235 }
2236 // From here on out, the PatchList is usable...
2237
2238#ifdef QUILT_TYPE_1
2239 if (!m_bquiltanyproj) {
2240 // Establish the quilt projection type
2241 m_quilt_proj = PROJECTION_MERCATOR; // default
2242 ChartBase *ppc = GetLargestScaleChart();
2243 if (ppc) m_quilt_proj = ppc->GetChartProjectionType();
2244 }
2245#endif
2246
2247 if (!m_bquiltanyproj) {
2248 // Walk the PatchList, marking any entries whose projection does not
2249 // match the determined quilt projection
2250 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2251 wxPatchListNode *pcinode = m_PatchList.Item(i);
2252 QuiltPatch *piqp = pcinode->GetData();
2253 if ((piqp->ProjType != m_quilt_proj) &&
2254 (piqp->ProjType != PROJECTION_UNKNOWN))
2255 piqp->b_Valid = false;
2256 }
2257 }
2258
2259 // Walk the PatchList, marking any entries which appear in the noshow array
2260 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2261 wxPatchListNode *pcinode = m_PatchList.Item(i);
2262 QuiltPatch *piqp = pcinode->GetData();
2263 for (unsigned int ins = 0;
2264 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
2265 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
2266 piqp->dbIndex) // chart is in the noshow list
2267 {
2268 piqp->b_Valid = false;
2269 break;
2270 }
2271 }
2272 }
2273
2274 // Generate the final render regions for the patches, one by one
2275
2276 m_covered_region.Clear();
2277#if 1 // this does the same as before with a lot less operations if there are
2278 // many charts
2279
2280 // If the reference chart is cm93, we need to render it first.
2281 bool b_skipCM93 = false;
2282 if (m_reference_type == CHART_TYPE_CM93COMP) {
2283 // find cm93 in the list
2284 for (int i = m_PatchList.GetCount() - 1; i >= 0; i--) {
2285 wxPatchListNode *pcinode = m_PatchList.Item(i);
2286 QuiltPatch *piqp = pcinode->GetData();
2287 if (!piqp->b_Valid) // skip invalid entries
2288 continue;
2289
2290 const ChartTableEntry &m = ChartData->GetChartTableEntry(piqp->dbIndex);
2291
2292 if (m.GetChartType() == CHART_TYPE_CM93COMP) {
2293 // Start with the chart's full region coverage.
2294 piqp->ActiveRegion = piqp->quilt_region;
2295 piqp->ActiveRegion.Intersect(cvp_region);
2296
2297 // Update the next pass full region to remove the region just
2298 // allocated
2299 m_covered_region.Union(piqp->quilt_region);
2300
2301 b_skipCM93 = true; // did this already...
2302 break;
2303 }
2304 }
2305 }
2306
2307 // Proceeding from largest scale to smallest....
2308
2309 for (int i = m_PatchList.GetCount() - 1; i >= 0; i--) {
2310 wxPatchListNode *pcinode = m_PatchList.Item(i);
2311 QuiltPatch *piqp = pcinode->GetData();
2312 if (!piqp->b_Valid) // skip invalid entries
2313 continue;
2314
2315 const ChartTableEntry &cte = ChartData->GetChartTableEntry(piqp->dbIndex);
2316
2317 if (b_skipCM93) {
2318 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
2319 }
2320
2321 // Start with the chart's full region coverage.
2322 piqp->ActiveRegion = piqp->quilt_region;
2323
2324 // this operation becomes expensive with lots of charts
2325 if (!b_has_overlays && m_PatchList.GetCount() < 25)
2326 piqp->ActiveRegion.Subtract(m_covered_region);
2327
2328 piqp->ActiveRegion.Intersect(cvp_region);
2329
2330 // Could happen that a larger scale chart covers completely a smaller
2331 // scale chart
2332 if (piqp->ActiveRegion.Empty() && (piqp->dbIndex != m_refchart_dbIndex))
2333 piqp->b_eclipsed = true;
2334
2335 // Maintain the present full quilt coverage region
2336 piqp->b_overlay = false;
2337 if (cte.GetChartFamily() == CHART_FAMILY_VECTOR) {
2338 piqp->b_overlay = s57chart::IsCellOverlayType(cte.GetFullSystemPath());
2339 }
2340
2341 if (!piqp->b_overlay) m_covered_region.Union(piqp->quilt_region);
2342 }
2343#else
2344 // this is the old algorithm does the same thing in n^2/2 operations instead
2345 // of 2*n-1
2346 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2347 wxPatchListNode *pcinode = m_PatchList.Item(i);
2348 QuiltPatch *piqp = pcinode->GetData();
2349
2350 if (!piqp->b_Valid) // skip invalid entries
2351 continue;
2352
2353 const ChartTableEntry &ctei = ChartData->GetChartTableEntry(piqp->dbIndex);
2354
2355 // Start with the chart's full region coverage.
2356 LLRegion vpr_region = piqp->quilt_region;
2357
2358 // This clause should be moved into the rendering routine for quilts so that
2359 // the actual region logic need only be applied to the render region
2360#if 1 // This clause went away with full-screen quilting
2361 // ...and came back with OpenGL....
2362
2363 // fetch and subtract regions for all larger scale charts
2364 for (unsigned int k = i + 1; k < m_PatchList.GetCount(); k++) {
2365 wxPatchListNode *pnode = m_PatchList.Item(k);
2366 QuiltPatch *pqp = pnode->GetData();
2367
2368 if (!pqp->b_Valid) // skip invalid entries
2369 continue;
2370
2379
2381
2385 // if( ( CHART_TYPE_S57 != ctei.GetChartType() ))
2386 if (!b_has_overlays) {
2387 if (!vpr_region.Empty()) {
2388 const ChartTableEntry &cte =
2389 ChartData->GetChartTableEntry(pqp->dbIndex);
2390 LLRegion larger_scale_chart_region =
2391 pqp->quilt_region; // GetChartQuiltRegion( cte, vp_local );
2392
2393 vpr_region.Subtract(larger_scale_chart_region);
2394 }
2395 }
2396 }
2397#endif
2398
2399 // Whatever is left in the vpr region and has not been yet rendered must
2400 // belong to the current target chart
2401
2402 wxPatchListNode *pinode = m_PatchList.Item(i);
2403 QuiltPatch *pqpi = pinode->GetData();
2404 pqpi->ActiveRegion = vpr_region;
2405
2406 // Move the active region so that upper left is 0,0 in final render
2407 // region
2408 // pqpi->ActiveRegion.Offset( -vp_local.rv_rect.x,
2409 // -vp_local.rv_rect.y );
2410
2411 // Could happen that a larger scale chart covers completely a smaller
2412 // scale chart
2413 if (pqpi->ActiveRegion.Empty()) pqpi->b_eclipsed = true;
2414
2415 // Update the next pass full region to remove the region just allocated
2416 // if( !vpr_region.Empty() )
2417 // unrendered_region.Subtract( vpr_region );
2418
2419 // Maintain the present full quilt coverage region
2420 // if( !pqpi->ActiveRegion.Empty() )
2421 m_covered_region.Union(pqpi->ActiveRegion);
2422 }
2423#endif
2424 // Restore temporary VP Rotation
2425 // vp_local.SetRotationAngle( saved_vp_rotation );
2426
2427 // Walk the list again, removing any entries marked as eclipsed....
2428 unsigned int il = 0;
2429 while (il < m_PatchList.GetCount()) {
2430 wxPatchListNode *pcinode = m_PatchList.Item(il);
2431 QuiltPatch *piqp = pcinode->GetData();
2432 if (piqp->b_eclipsed) {
2433 // Make sure that this chart appears in the eclipsed list...
2434 // This can happen when....
2435 bool b_noadd = false;
2436 for (unsigned int ir = 0; ir < m_eclipsed_stack_array.size(); ir++) {
2437 if (piqp->dbIndex == m_eclipsed_stack_array[ir]) {
2438 b_noadd = true;
2439 break;
2440 }
2441 }
2442 if (!b_noadd) m_eclipsed_stack_array.push_back(piqp->dbIndex);
2443
2444 m_PatchList.DeleteNode(pcinode);
2445 il = 0; // restart the list walk
2446 }
2447
2448 else
2449 il++;
2450 }
2451 // Mark the quilt to indicate need for background clear if the region is
2452 // not fully covered
2453 // m_bneed_clear = !unrendered_region.Empty();
2454 // m_back_region = unrendered_region;
2455
2456 // Finally, iterate thru the quilt and preload all of the required charts.
2457 // For dynamic S57 SENC creation, this is where SENC creation happens
2458 // first.....
2459
2460 // Stop (temporarily) canvas paint events, since some chart loads mught
2461 // Yield(), thus causing performance loss on recursion We will (always??) get
2462 // a refresh on the new Quilt anyway...
2463 m_parent->EnablePaint(false);
2464
2465 // Load and lock all required charts
2466 // First lock required charts already in the cache
2467 // otherwise under memory pressure if chart1 and chart2
2468 // are in the quilt loading chart1 could evict chart2
2469 //
2470 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2471 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2472 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2473 if (!ChartData->IsChartLocked(pqc->dbIndex))
2474 ChartData->LockCacheChart(pqc->dbIndex);
2475 }
2476 }
2477
2478 // Now load and lock any new charts required by the quilt
2479 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2480 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2481 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2482 if (!ChartData->IsChartLocked(pqc->dbIndex)) // Not locked, or not loaded
2483 ChartData->OpenChartFromDBAndLock(pqc->dbIndex, FULL_INIT, true);
2484 }
2485 }
2486
2487#if 0
2488 // first lock charts already in the cache
2489 // otherwise under memory pressure if chart1 and chart2
2490 // are in the quilt loading chart1 could evict chart2
2491 //
2492 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2493 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2494 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2495 if (ChartData->IsChartLocked(pqc->dbIndex)) // already locked
2496 pqc->b_locked = true;
2497 else
2498 pqc->b_locked = ChartData->LockCacheChart(pqc->dbIndex);
2499 }
2500 }
2501
2502 // open charts not in the cache
2503 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2504 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2505 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2506 // I am fairly certain this test can now be removed
2507 // with improved smooth movement logic
2508 // if( !ChartData->IsChartInCache( pqc->dbIndex ) )
2509 // b_stop_movement = true;
2510 // only lock chart if not already locked
2511 if (ChartData->OpenChartFromDBAndLock(pqc->dbIndex, FULL_INIT,
2512 !pqc->b_locked))
2513 pqc->b_locked = true;
2514 }
2515 }
2516#endif
2517
2518 m_parent->EnablePaint(true);
2519 // Build and maintain the array of indexes in this quilt
2520
2521 m_last_index_array = m_index_array; // save the last one for delta checks
2522
2523 m_index_array.clear();
2524
2525 // The index array is to be built in reverse, largest scale first
2526 unsigned int kl = m_PatchList.GetCount();
2527 for (unsigned int k = 0; k < kl; k++) {
2528 wxPatchListNode *cnode = m_PatchList.Item((kl - k) - 1);
2529 m_index_array.push_back(cnode->GetData()->dbIndex);
2530 cnode = cnode->GetNext();
2531 }
2532
2533 // Walk the patch list again, checking the depth units
2534 // If they are all the same, then the value is usable
2535
2536 m_quilt_depth_unit = "";
2537 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
2538 if (pc) {
2539 m_quilt_depth_unit = pc->GetDepthUnits();
2540
2541 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2542 int units = ps52plib->m_nDepthUnitDisplay;
2543 switch (units) {
2544 case 0:
2545 m_quilt_depth_unit = "Feet";
2546 break;
2547 case 1:
2548 m_quilt_depth_unit = "Meters";
2549 break;
2550 case 2:
2551 m_quilt_depth_unit = "Fathoms";
2552 break;
2553 }
2554 }
2555 }
2556
2557 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2558 wxPatchListNode *pnode = m_PatchList.Item(k);
2559 QuiltPatch *pqp = pnode->GetData();
2560
2561 if (!pqp->b_Valid) // skip invalid entries
2562 continue;
2563
2564 ChartBase *pc = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2565 if (pc) {
2566 wxString du = pc->GetDepthUnits();
2567 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2568 int units = ps52plib->m_nDepthUnitDisplay;
2569 switch (units) {
2570 case 0:
2571 du = "Feet";
2572 break;
2573 case 1:
2574 du = "Meters";
2575 break;
2576 case 2:
2577 du = "Fathoms";
2578 break;
2579 }
2580 }
2581 wxString dul = du.Lower();
2582 wxString ml = m_quilt_depth_unit.Lower();
2583
2584 if (dul != ml) {
2585 // Try all the odd cases
2586 if (dul.StartsWith("meters") && ml.StartsWith("meters"))
2587 continue;
2588 else if (dul.StartsWith("metres") && ml.StartsWith("metres"))
2589 continue;
2590 else if (dul.StartsWith("fathoms") && ml.StartsWith("fathoms"))
2591 continue;
2592 else if (dul.StartsWith("met") && ml.StartsWith("met"))
2593 continue;
2594
2595 // They really are different
2596 m_quilt_depth_unit = "";
2597 break;
2598 }
2599 }
2600 }
2601
2602 // And try to prove that all required charts are in the cache
2603 // If one is missing, try to load it
2604 // If still missing, remove its patch from the quilt
2605 // This will probably leave a "black hole" in the quilt...
2606 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2607 wxPatchListNode *pnode = m_PatchList.Item(k);
2608 QuiltPatch *pqp = pnode->GetData();
2609
2610 if (pqp->b_Valid) {
2611 if (!ChartData->IsChartInCache(pqp->dbIndex)) {
2612 wxLogMessage(" Quilt Compose cache miss...");
2613 ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2614 if (!ChartData->IsChartInCache(pqp->dbIndex)) {
2615 wxLogMessage(" Oops, removing from quilt...");
2616 pqp->b_Valid = false;
2617 }
2618 }
2619 }
2620 }
2621
2622 // Make sure the reference chart is in the cache
2623 if (!ChartData->IsChartInCache(m_refchart_dbIndex))
2624 ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
2625
2626 // Walk the patch list again, checking the error factor
2627 // Also, directly mark the patch to indicate if it should be treated as an
2628 // overlay as seen in Austrian Inland series
2629
2630 m_bquilt_has_overlays = false;
2631 m_max_error_factor = 0.;
2632 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2633 wxPatchListNode *pnode = m_PatchList.Item(k);
2634 QuiltPatch *pqp = pnode->GetData();
2635
2636 if (!pqp->b_Valid) // skip invalid entries
2637 continue;
2638
2639 ChartBase *pc = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2640 if (pc) {
2641 m_max_error_factor =
2642 wxMax(m_max_error_factor, pc->GetChart_Error_Factor());
2643 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2644 bool isOverlay = IsChartS57Overlay(pqp->dbIndex);
2645 pqp->b_overlay = isOverlay;
2646 if (isOverlay) m_bquilt_has_overlays = true;
2647 }
2648 }
2649 }
2650
2651 m_bcomposed = true;
2652
2653 m_vp_quilt = vp_in; // save the corresponding ViewPort locally
2654
2655 ChartData->LockCache();
2656
2657 // Create and store a hash value representing the contents of the
2658 // m_extended_stack_array
2659 unsigned long xa_hash = 5381;
2660 for (unsigned int im = 0; im < m_extended_stack_array.size(); im++) {
2661 int dbindex = m_extended_stack_array[im];
2662 xa_hash = ((xa_hash << 5) + xa_hash) + dbindex; /* hash * 33 + dbindex */
2663 }
2664
2665 m_xa_hash = xa_hash;
2666 m_bbusy = false;
2667 return true;
2668}
2669
2670// Compute and update the member quilt render region, considering all scale
2671// factors, group exclusions, etc.
2672void Quilt::ComputeRenderRegion(ViewPort &vp, OCPNRegion &chart_region) {
2673 if (!m_bcomposed) return;
2674
2675 OCPNRegion rendered_region;
2676
2677 if (GetnCharts() && !m_bbusy && !chart_region.Empty()) {
2678 // Walk the quilt, considering each chart from smallest scale to largest
2679
2680 ChartBase *chart = GetFirstChart();
2681
2682 while (chart) {
2683 if (!(chart->GetChartProjectionType() != PROJECTION_MERCATOR &&
2684 vp.b_MercatorProjectionOverride)) {
2685 QuiltPatch *pqp = GetCurrentPatch();
2686 if (pqp->b_Valid) {
2687 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2688 chart_region, pqp->ActiveRegion, chart->GetNativeScale());
2689 if (!get_screen_region.Empty())
2690 rendered_region.Union(get_screen_region);
2691 }
2692 }
2693 chart = GetNextChart();
2694 }
2695 }
2696 // Record the region actually rendered
2697 m_rendered_region = rendered_region;
2698}
2699
2700int g_render;
2701
2702bool Quilt::RenderQuiltRegionViewOnDCNoText(wxMemoryDC &dc, ViewPort &vp,
2703 OCPNRegion &chart_region) {
2704 return DoRenderQuiltRegionViewOnDC(dc, vp, chart_region);
2705}
2706
2707bool Quilt::RenderQuiltRegionViewOnDCTextOnly(wxMemoryDC &dc, ViewPort &vp,
2708 OCPNRegion &chart_region) {
2709 return DoRenderQuiltRegionViewOnDCTextOnly(dc, vp, chart_region);
2710}
2711
2712bool Quilt::DoRenderQuiltRegionViewOnDC(wxMemoryDC &dc, ViewPort &vp,
2713 OCPNRegion &chart_region) {
2714#ifdef ocpnUSE_DIBSECTION
2715 ocpnMemDC tmp_dc;
2716#else
2717 wxMemoryDC tmp_dc;
2718#endif
2719
2720 if (!m_bcomposed) return false;
2721
2722 OCPNRegion rendered_region;
2723
2724 if (GetnCharts() && !m_bbusy) {
2725 OCPNRegion screen_region = chart_region;
2726
2727 // Walk the quilt, drawing each chart from smallest scale to largest
2728 // Render the quilt's charts onto a temp dc
2729 // and blit the active region rectangles to to target dc, one-by-one
2730
2731 ChartBase *chart = GetFirstChart();
2732 int chartsDrawn = 0;
2733
2734 if (!chart_region.Empty()) {
2735 while (chart) {
2736 bool okToRender = true;
2737
2738 if (chart->GetChartProjectionType() != PROJECTION_MERCATOR &&
2739 vp.b_MercatorProjectionOverride)
2740 okToRender = false;
2741
2742 if (!okToRender) {
2743 chart = GetNextChart();
2744 continue;
2745 }
2746 QuiltPatch *pqp = GetCurrentPatch();
2747 if (pqp->b_Valid) {
2748 bool b_chart_rendered = false;
2749 LLRegion get_region = pqp->ActiveRegion;
2750
2751 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2752 chart_region, get_region, chart->GetNativeScale());
2753 if (!get_screen_region.Empty()) {
2754 if (!pqp->b_overlay) {
2755 if (chart->GetChartType() == CHART_TYPE_CM93COMP) {
2756 b_chart_rendered =
2757 chart->RenderRegionViewOnDC(tmp_dc, vp, get_screen_region);
2758 } else {
2759 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2760 if (Chs57) {
2761 if (Chs57->m_RAZBuilt) {
2762 b_chart_rendered = Chs57->RenderRegionViewOnDCNoText(
2763 tmp_dc, vp, get_screen_region);
2764 }
2765 } else {
2766 ChartPlugInWrapper *ChPI =
2767 dynamic_cast<ChartPlugInWrapper *>(chart);
2768 if (ChPI) {
2769 b_chart_rendered = ChPI->RenderRegionViewOnDCNoText(
2770 tmp_dc, vp, get_screen_region);
2771 } else
2772 b_chart_rendered = chart->RenderRegionViewOnDC(
2773 tmp_dc, vp, get_screen_region);
2774
2775 b_chart_rendered = true;
2776 }
2777 }
2778
2779 // if( chart->GetChartType() !=
2780 // CHART_TYPE_CM93COMP )
2781 // b_chart_rendered = true;
2782 screen_region.Subtract(get_screen_region);
2783 }
2784 }
2785
2786 OCPNRegionIterator upd(get_screen_region);
2787 while (upd.HaveRects()) {
2788 wxRect rect = upd.GetRect();
2789 dc.Blit(rect.x, rect.y, rect.width, rect.height, &tmp_dc, rect.x,
2790 rect.y, wxCOPY, true);
2791 upd.NextRect();
2792 }
2793
2794 tmp_dc.SelectObject(wxNullBitmap);
2795
2796 if (b_chart_rendered) rendered_region.Union(get_screen_region);
2797 }
2798
2799 chartsDrawn++;
2800 chart = GetNextChart();
2801 }
2802 }
2803
2804 if (!chartsDrawn) m_parent->GetVP().SetProjectionType(PROJECTION_MERCATOR);
2805
2806 // Render any Overlay patches for s57 charts(cells)
2807 if (m_bquilt_has_overlays && !chart_region.Empty()) {
2808 chart = GetFirstChart();
2809 while (chart) {
2810 QuiltPatch *pqp = GetCurrentPatch();
2811 if (pqp->b_Valid) {
2812 if (pqp->b_overlay) {
2813 LLRegion get_region = pqp->ActiveRegion;
2814 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2815 chart_region, get_region, chart->GetNativeScale());
2816 if (!get_region.Empty()) {
2817 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2818 if (Chs57) {
2819 Chs57->RenderOverlayRegionViewOnDC(tmp_dc, vp,
2820 get_screen_region);
2821 } else {
2822 ChartPlugInWrapper *ChPI =
2823 dynamic_cast<ChartPlugInWrapper *>(chart);
2824 if (ChPI) {
2825 ChPI->RenderRegionViewOnDC(tmp_dc, vp, get_screen_region);
2826 }
2827 }
2828
2829 OCPNRegionIterator upd(get_screen_region);
2830 while (upd.HaveRects()) {
2831 wxRect rect = upd.GetRect();
2832 dc.Blit(rect.x, rect.y, rect.width, rect.height, &tmp_dc,
2833 rect.x, rect.y, wxCOPY, true);
2834 upd.NextRect();
2835 }
2836 tmp_dc.SelectObject(wxNullBitmap);
2837 }
2838 }
2839 }
2840
2841 chart = GetNextChart();
2842 }
2843 }
2844
2845 // Any part of the chart region that was not rendered in the loop needs
2846 // to be cleared
2847 OCPNRegionIterator clrit(screen_region);
2848 while (clrit.HaveRects()) {
2849 wxRect rect = clrit.GetRect();
2850#ifdef __WXOSX__
2851 dc.SetPen(*wxBLACK_PEN);
2852 dc.SetBrush(*wxBLACK_BRUSH);
2853 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height);
2854#else
2855 dc.Blit(rect.x, rect.y, rect.width, rect.height, &dc, rect.x, rect.y,
2856 wxCLEAR);
2857#endif
2858 clrit.NextRect();
2859 }
2860
2861 // Highlighting....
2862 if (m_nHiLiteIndex >= 0) {
2863 OCPNRegion hiregion =
2864 vp.GetVPRegionIntersect(chart_region, GetHiliteRegion(), 1);
2865 wxRect box = hiregion.GetBox();
2866
2867 if (!box.IsEmpty()) {
2868 // Is scratch member bitmap OK?
2869 if (m_pBM) {
2870 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2871 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2872 delete m_pBM;
2873 m_pBM = NULL;
2874 }
2875 }
2876
2877 if (NULL == m_pBM)
2878 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2879
2880 // Copy the entire quilt to my scratch bm
2881 wxMemoryDC q_dc;
2882 q_dc.SelectObject(*m_pBM);
2883 q_dc.Blit(0, 0, vp.rv_rect.width, vp.rv_rect.height, &dc, 0, 0);
2884 q_dc.SelectObject(wxNullBitmap);
2885
2886 // Create a "mask" bitmap from the chart's region
2887 // WxGTK has an error in this method....Creates a color bitmap, not
2888 // usable for mask creation So, I clone with correction
2889 wxBitmap hl_mask_bm(vp.rv_rect.width, vp.rv_rect.height, 1);
2890 wxMemoryDC mdc;
2891 mdc.SelectObject(hl_mask_bm);
2892 mdc.SetBackground(*wxBLACK_BRUSH);
2893 mdc.Clear();
2894 mdc.SetClippingRegion(box);
2895 mdc.SetBackground(*wxWHITE_BRUSH);
2896 mdc.Clear();
2897 mdc.SelectObject(wxNullBitmap);
2898
2899 if (hl_mask_bm.IsOk()) {
2900 wxMask *phl_mask = new wxMask(hl_mask_bm);
2901 m_pBM->SetMask(phl_mask);
2902 q_dc.SelectObject(*m_pBM);
2903
2904 // Create another mask, dc and bitmap for red-out
2905 wxBitmap rbm(vp.rv_rect.width, vp.rv_rect.height);
2906 wxMask *pr_mask = new wxMask(hl_mask_bm);
2907 wxMemoryDC rdc;
2908 rbm.SetMask(pr_mask);
2909 rdc.SelectObject(rbm);
2910 unsigned char hlcolor = 255;
2911 switch (global_color_scheme) {
2912 case GLOBAL_COLOR_SCHEME_DAY:
2913 hlcolor = 255;
2914 break;
2915 case GLOBAL_COLOR_SCHEME_DUSK:
2916 hlcolor = 64;
2917 break;
2918 case GLOBAL_COLOR_SCHEME_NIGHT:
2919 hlcolor = 16;
2920 break;
2921 default:
2922 hlcolor = 255;
2923 break;
2924 }
2925
2926 rdc.SetBackground(wxBrush(wxColour(hlcolor, 0, 0)));
2927 rdc.Clear();
2928
2929 OCPNRegionIterator upd(hiregion);
2930 while (upd.HaveRects()) {
2931 wxRect rect = upd.GetRect();
2932 rdc.Blit(rect.x, rect.y, rect.width, rect.height, &q_dc, rect.x,
2933 rect.y, wxOR, true);
2934 upd.NextRect();
2935 }
2936
2937 OCPNRegionIterator updq(hiregion);
2938 while (updq.HaveRects()) {
2939 wxRect rect = updq.GetRect();
2940 q_dc.Blit(rect.x, rect.y, rect.width, rect.height, &rdc, rect.x,
2941 rect.y, wxCOPY, true);
2942 updq.NextRect();
2943 }
2944
2945 q_dc.SelectObject(wxNullBitmap);
2946 m_pBM->SetMask(NULL);
2947
2948 // Select the scratch BM as the return dc contents
2949 dc.SelectObject(*m_pBM);
2950
2951 // Clear the rdc
2952 rdc.SelectObject(wxNullBitmap);
2953 }
2954 } // box not empty
2955 } // m_nHiLiteIndex
2956
2957 // Fogging....
2958 if (g_fog_overzoom) {
2959 double scale_factor = vp.ref_scale / vp.chart_scale;
2960
2961 if (scale_factor > g_overzoom_emphasis_base) {
2962 float fog = ((scale_factor - g_overzoom_emphasis_base) * 255.) / 20.;
2963 fog = wxMin(fog, 200.); // Don't fog out completely
2964
2965 // Is scratch member bitmap OK?
2966 if (m_pBM) {
2967 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2968 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2969 delete m_pBM;
2970 m_pBM = NULL;
2971 }
2972 }
2973
2974 if (NULL == m_pBM)
2975 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2976
2977 // Copy the entire quilt to my scratch bm
2978 wxMemoryDC q_dc;
2979 q_dc.SelectObject(*m_pBM);
2980 q_dc.Blit(0, 0, vp.rv_rect.width, vp.rv_rect.height, &dc, 0, 0);
2981 q_dc.SelectObject(wxNullBitmap);
2982
2983 wxImage src = m_pBM->ConvertToImage();
2984#if 1
2985 int blur_factor =
2986 wxMin((scale_factor - g_overzoom_emphasis_base) / 4, 4);
2987 if (src.IsOk()) {
2988 wxImage dest = src.Blur(blur_factor);
2989#endif
2990
2991#if 0 // this is fogging effect
2992 unsigned char *bg = src.GetData();
2993 wxColour color = m_parent->GetFogColor();
2994
2995 float transparency = fog;
2996
2997 // destination image
2998 wxImage dest(vp.rv_rect.width, vp.rv_rect.height);
2999 unsigned char *dest_data = (unsigned char *) malloc( vp.rv_rect.width * vp.rv_rect.height * 3 * sizeof(unsigned char) );
3000 unsigned char *d = dest_data;
3001
3002 float alpha = 1.0 - (float)transparency / 255.0;
3003 int sb = vp.rv_rect.width * vp.rv_rect.height;
3004 for( int i = 0; i < sb; i++ ) {
3005 float a = alpha;
3006
3007 int r = ( ( *bg++ ) * a ) + (1.0-a) * color.Red();
3008 *d++ = r;
3009 int g = ( ( *bg++ ) * a ) + (1.0-a) * color.Green();
3010 *d++ = g;
3011 int b = ( ( *bg++ ) * a ) + (1.0-a) * color.Blue();
3012 *d++ = b;
3013 }
3014
3015 dest.SetData( dest_data );
3016#endif
3017
3018 wxBitmap dim(dest);
3019 wxMemoryDC ddc;
3020 ddc.SelectObject(dim);
3021
3022 q_dc.SelectObject(*m_pBM);
3023 OCPNRegionIterator upd(rendered_region);
3024 while (upd.HaveRects()) {
3025 wxRect rect = upd.GetRect();
3026 q_dc.Blit(rect.x, rect.y, rect.width, rect.height, &ddc, rect.x,
3027 rect.y);
3028 upd.NextRect();
3029 }
3030
3031 ddc.SelectObject(wxNullBitmap);
3032 q_dc.SelectObject(wxNullBitmap);
3033
3034 // Select the scratch BM as the return dc contents
3035 dc.SelectObject(*m_pBM);
3036 }
3037 }
3038 } // overzoom
3039
3040 if (!dc.IsOk()) // some error, probably bad charts, to be disabled on next
3041 // compose
3042 {
3043 SubstituteClearDC(dc, vp);
3044 }
3045
3046 } else { // no charts yet, or busy....
3047 SubstituteClearDC(dc, vp);
3048 }
3049
3050 // Record the region actually rendered
3051 m_rendered_region = rendered_region;
3052
3053 m_vp_rendered = vp;
3054 return true;
3055}
3056
3057void Quilt::SubstituteClearDC(wxMemoryDC &dc, ViewPort &vp) {
3058 if (m_pBM) {
3059 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
3060 (m_pBM->GetHeight() != vp.rv_rect.height)) {
3061 delete m_pBM;
3062 m_pBM = NULL;
3063 }
3064 }
3065
3066 if (NULL == m_pBM) {
3067 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
3068 }
3069
3070 dc.SelectObject(wxNullBitmap);
3071 dc.SelectObject(*m_pBM);
3072 dc.SetBackground(*wxBLACK_BRUSH);
3073 dc.Clear();
3074 m_covered_region.Clear();
3075}
3076
3077bool Quilt::DoRenderQuiltRegionViewOnDCTextOnly(wxMemoryDC &dc, ViewPort &vp,
3078 OCPNRegion &chart_region) {
3079 if (!m_bcomposed) return false;
3080
3081 OCPNRegion rendered_region;
3082
3083 if (GetnCharts() && !m_bbusy) {
3084 OCPNRegion screen_region = chart_region;
3085
3086 // Walk the quilt, drawing each chart from largest scale to smallest
3087
3088 ChartBase *chart = GetLargestScaleChart();
3089
3090 while (chart) {
3091 QuiltPatch *pqp = GetCurrentPatch();
3092 if (pqp->b_Valid) {
3093 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
3094 if (Chs57)
3095 Chs57->RenderRegionViewOnDCTextOnly(dc, vp, chart_region);
3096 else {
3097 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
3098 if (ChPI) {
3099 ChPI->RenderRegionViewOnDCTextOnly(dc, vp, chart_region);
3100 }
3101 }
3102 }
3103
3104 chart = GetNextSmallerScaleChart();
3105 }
3106
3107 } else { // no charts yet, or busy....
3108 SubstituteClearDC(dc, vp);
3109 }
3110
3111 return true;
3112}
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:1775
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:187