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