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