OpenCPN Partial API docs
All Classes Namespaces Functions Variables Pages
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 return scale * 4 * mod; // MBTiles are fast enough
849 else
850 return scale * 1 * mod;
851 }
852
853 case CHART_FAMILY_VECTOR: {
854 return scale * 4 * mod;
855 }
856
857 default: {
858 mod = wxMin(mod, 2.0);
859 return scale * 2 * mod;
860 }
861 }
862}
863
864struct scale {
865 int index, nom, min, max;
866};
867
868int Quilt::AdjustRefOnZoom(bool b_zin, ChartFamilyEnum family,
869 ChartTypeEnum type, double proposed_scale_onscreen) {
870 std::vector<scale> scales;
871 std::vector<scale> scales_mbtiles;
872
873 // For Vector charts, or for ZoomIN operations, we can switch to any chart
874 // that is on screen. Otherwise, we can only switch to charts contining the
875 // VP center point
876
877 // Since this rule is mainly for preservation of performance,
878 // we can also allow fullscreen reference chart selection if opengl is active
879
880 bool b_allow_fullscreen_ref =
881 (family == CHART_FAMILY_VECTOR) || b_zin || g_bopengl;
882
883 // Get the scale of the smallest scale
884 // chart, of the current type, in the quilt
885 int smallest_scale = 1;
886 for (size_t i = 0; i < m_extended_stack_array.size(); i++) {
887 int index = m_extended_stack_array[i];
888 if (ChartData->GetDBChartType(index) == type)
889 smallest_scale = wxMax(smallest_scale, ChartData->GetDBChartScale(index));
890 }
891
892 // Walk the extended chart array, capturing data
893 int i_first = 0;
894 for (size_t i = 0; i < m_extended_stack_array.size(); i++) {
895 int test_db_index = m_extended_stack_array[i];
896
897 if (b_allow_fullscreen_ref ||
898 m_parent->GetpCurrentStack()->DoesStackContaindbIndex(test_db_index)) {
899 if ((family == ChartData->GetDBChartFamily(test_db_index)) &&
900 IsChartQuiltableRef(test_db_index)
901 /*&& !IsChartS57Overlay( test_db_index )*/) {
902 int nscale = ChartData->GetDBChartScale(test_db_index);
903
904 int nmax_scale = GetNomScaleMax(nscale, type, family);
905
906 // For the largest scale chart, allow essentially infinite overzoom.
907 // The range will be clipped later
908 if (0 == i_first) nmax_scale = 1;
909
910 int nmin_scale = GetNomScaleMin(nscale, type, family);
911
912 // Allow RNC quilt to zoom far out and still show smallest scale chart.
913 if((type == CHART_TYPE_KAP) && (nscale == smallest_scale))
914 nmin_scale *= 24;
915
916 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(test_db_index))
917 scales_mbtiles.push_back(
918 scale{test_db_index, nscale, nmin_scale, nmax_scale});
919 else
920 scales.push_back(
921 scale{test_db_index, nscale, nmin_scale, nmax_scale});
922
923 i_first++;
924 }
925 }
926 }
927 // mbtiles charts only set
928 if (scales.empty()) scales = scales_mbtiles;
929 // If showing Vector charts,
930 // Find the smallest scale chart of the target type (i.e. skipping cm93)
931 // and make sure that its min scale is at least
932 // small enough to allow reasonable zoomout.
933 // But this will be calculated without regard to zoom scale factor, so that
934 // the piano does not grow excessively
935 if (CHART_FAMILY_VECTOR == family) {
936 for (size_t i = scales.size(); i; i--) {
937 int test_db_index = scales[i - 1].index;
938 if (type == ChartData->GetDBChartType(test_db_index)) {
939 scales[i - 1].min = scales[i - 1].nom * 80;
940 break;
941 }
942 }
943 }
944
945 // Traverse the list, making sure that the allowable scale ranges overlap so
946 // as to make no "holes" in the coverage. We do this by extending upward the
947 // range of larger scale charts, so that they overlap the next smaller scale
948 // chart. Makes a nicer image... However, we don't want excessive underzoom,
949 // for performance reasons. So make sure any adjusted min_scale is not more
950 // than twice the already established value
951 if (scales.size() > 1) {
952 for (unsigned i = 0; i < scales.size() - 1; i++) {
953 int min_scale_test = wxMax(scales[i].min, scales[i + 1].max + 1);
954 min_scale_test = wxMin(min_scale_test, scales[i].min * 2);
955 scales[i].min = min_scale_test;
956 // min_scale[i] = wxMax(min_scale[i], max_scale.Item(i+1) +
957 // 1);
958 }
959 }
960
961 // There may still be holes...
962 // Traverse the list again, from smaller to larger scale, filling in any holes
963 // by increasing the max_scale of smaller scale charts. Skip cm93 if present
964 if (scales.size() > 2) {
965 for (size_t i = scales.size() - 2; i >= 1; i--) {
966 scales[i].max = wxMin(scales[i].max, scales[i - 1].min - 1);
967 }
968 }
969
970 int new_ref_dbIndex = -1;
971
972 // Search for the largest scale chart whose scale limits contain the requested
973 // scale.
974 for (size_t i = 0; i < scales.size(); i++) {
975 if ((proposed_scale_onscreen <
976 scales[i].min *
977 1.05) && // 5 percent leeway to allow for roundoff errors
978 (proposed_scale_onscreen > scales[i].max)) {
979 new_ref_dbIndex = scales[i].index;
980 break;
981 }
982 }
983
984 return new_ref_dbIndex;
985}
986
987int Quilt::AdjustRefOnZoomOut(double proposed_scale_onscreen) {
988 // Reset "lost" chart logic
989 m_lost_refchart_dbIndex = -1;
990
991 int current_db_index = m_refchart_dbIndex;
992 int current_family = m_reference_family;
993 ChartTypeEnum current_type = (ChartTypeEnum)m_reference_type;
994
995 if (m_refchart_dbIndex >= 0) {
996 const ChartTableEntry &cte =
997 ChartData->GetChartTableEntry(m_refchart_dbIndex);
998 current_family = cte.GetChartFamily();
999 current_type = (ChartTypeEnum)cte.GetChartType();
1000 }
1001
1002 if (current_type == CHART_TYPE_CM93COMP) return current_db_index;
1003
1004 int proposed_ref_index =
1005 AdjustRefOnZoom(false, (ChartFamilyEnum)current_family, current_type,
1006 proposed_scale_onscreen);
1007
1008 m_zout_family = -1;
1009 if (proposed_ref_index < 0) {
1010 m_zout_family = current_family; // save it
1011 m_zout_type = current_type;
1012 m_zout_dbindex = current_db_index;
1013 }
1014
1015 SetReferenceChart(proposed_ref_index);
1016
1017 return proposed_ref_index;
1018}
1019
1020int Quilt::AdjustRefOnZoomIn(double proposed_scale_onscreen) {
1021 // Reset "lost" chart logic
1022 m_lost_refchart_dbIndex = -1;
1023
1024 int current_db_index = m_refchart_dbIndex;
1025 int current_family = m_reference_family;
1026 ChartTypeEnum current_type = (ChartTypeEnum)m_reference_type;
1027
1028 if (m_zout_family >= 0) {
1029 current_type = (ChartTypeEnum)m_zout_type;
1030 current_family = m_zout_family;
1031 }
1032
1033 // If the current reference chart is cm93, and it became so due to a zout
1034 // from another family, detect this case and allow switch to save chart index
1035 // family
1036 if (current_type == CHART_TYPE_CM93COMP) {
1037 if (m_zout_family >= 0) {
1038 current_family = ChartData->GetDBChartFamily(m_zout_dbindex);
1039 } else // cm93 (selected) does not shift charts
1040 return current_db_index;
1041 }
1042
1043 if ((-1 == m_refchart_dbIndex) && (m_zout_dbindex >= 0))
1044 BuildExtendedChartStackAndCandidateArray(m_zout_dbindex, m_vp_quilt);
1045 else
1046 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, m_vp_quilt);
1047
1048 int proposed_ref_index =
1049 AdjustRefOnZoom(true, (ChartFamilyEnum)current_family, current_type,
1050 proposed_scale_onscreen);
1051
1052 if (current_db_index == -1) {
1053 SetReferenceChart(proposed_ref_index);
1054 return proposed_ref_index;
1055 }
1056
1057 if (proposed_ref_index != -1) {
1058 if (ChartData->GetDBChartScale(current_db_index) >=
1059 ChartData->GetDBChartScale(proposed_ref_index)) {
1060 SetReferenceChart(proposed_ref_index);
1061 return proposed_ref_index;
1062 }
1063 }
1064 proposed_ref_index = current_db_index;
1065
1066 SetReferenceChart(proposed_ref_index);
1067
1068 return proposed_ref_index;
1069}
1070
1071bool Quilt::IsChartSmallestScale(int dbIndex) {
1072 if (!ChartData) return false;
1073
1074 // find the smallest scale chart of the specified type on the extended stack
1075 // array
1076 int specified_type = ChartData->GetDBChartType(dbIndex);
1077 int target_dbindex = -1;
1078
1079 unsigned int target_stack_index = 0;
1080 if (m_extended_stack_array.size()) {
1081 while ((target_stack_index <= (m_extended_stack_array.size() - 1))) {
1082 int test_db_index = m_extended_stack_array[target_stack_index];
1083
1084 if (specified_type == ChartData->GetDBChartType(test_db_index))
1085 target_dbindex = test_db_index;
1086
1087 target_stack_index++;
1088 }
1089 }
1090 return (dbIndex == target_dbindex);
1091}
1092
1093LLRegion Quilt::GetHiliteRegion() {
1094 LLRegion r;
1095 if (m_nHiLiteIndex >= 0) {
1096 // Walk the PatchList, looking for the target hilite index
1097 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
1098 wxPatchListNode *pcinode = m_PatchList.Item(i);
1099 QuiltPatch *piqp = pcinode->GetData();
1100 if ((m_nHiLiteIndex == piqp->dbIndex) && (piqp->b_Valid)) // found it
1101 {
1102 r = piqp->ActiveRegion;
1103 break;
1104 }
1105 }
1106
1107 // If not in the patchlist, look in the full chartbar
1108 if (r.Empty()) {
1109 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1110 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1111 if (m_nHiLiteIndex == pqc->dbIndex) {
1112 LLRegion chart_region = pqc->GetCandidateRegion();
1113 if (!chart_region.Empty()) {
1114 // Do not highlite fully eclipsed charts
1115 bool b_eclipsed = false;
1116 for (unsigned int ir = 0; ir < m_eclipsed_stack_array.size();
1117 ir++) {
1118 if (m_nHiLiteIndex == m_eclipsed_stack_array[ir]) {
1119 b_eclipsed = true;
1120 break;
1121 }
1122 }
1123
1124 if (!b_eclipsed) r = chart_region;
1125 break;
1126 }
1127 }
1128 }
1129 }
1130
1131 // Might be MBTiles....
1132 if (r.Empty()) {
1133 const ChartTableEntry &cte =
1134 ChartData->GetChartTableEntry(m_nHiLiteIndex);
1135 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
1136 r = GetTilesetRegion(m_nHiLiteIndex);
1137 }
1138 }
1139 }
1140 return r;
1141}
1142
1143const LLRegion &Quilt::GetTilesetRegion(int dbIndex) {
1144 LLRegion world_region(-90, -180, 90, 180);
1145
1146 const ChartTableEntry &cte = ChartData->GetChartTableEntry(dbIndex);
1147 LLRegion &target_region = const_cast<LLRegion &>(cte.quilt_candidate_region);
1148
1149 if (!target_region.Empty()) return target_region;
1150
1151 // If the chart has an aux ply table, use it for finer region precision
1152 int nAuxPlyEntries = cte.GetnAuxPlyEntries();
1153 if (nAuxPlyEntries >= 1) {
1154 target_region.Clear();
1155 for (int ip = 0; ip < nAuxPlyEntries; ip++) {
1156 float *pfp = cte.GetpAuxPlyTableEntry(ip);
1157 int nAuxPly = cte.GetAuxCntTableEntry(ip);
1158
1159 target_region.Union(LLRegion(nAuxPly, pfp));
1160 }
1161 } else {
1162 std::vector<float> vec = ChartData->GetReducedPlyPoints(dbIndex);
1163
1164 std::vector<float> vecr;
1165 for (size_t i = 0; i < vec.size() / 2; i++) {
1166 float a = vec[i * 2 + 1];
1167 vecr.push_back(a);
1168 a = vec[i * 2];
1169 vecr.push_back(a);
1170 }
1171
1172 std::vector<float>::iterator it = vecr.begin();
1173
1174 if (vecr.size() / 2 >= 3) { // could happen with old database and some
1175 // charts, e.g. SHOM 2381.kap
1176
1177 target_region = LLRegion(vecr.size() / 2, (float *)&(*it));
1178 } else
1179 target_region = world_region;
1180 }
1181
1182 // Remove the NoCovr regions
1183 if (!target_region.Empty()) { // don't bother if the region is already empty
1184 int nNoCovrPlyEntries = cte.GetnNoCovrPlyEntries();
1185 if (nNoCovrPlyEntries) {
1186 for (int ip = 0; ip < nNoCovrPlyEntries; ip++) {
1187 float *pfp = cte.GetpNoCovrPlyTableEntry(ip);
1188 int nNoCovrPly = cte.GetNoCovrCntTableEntry(ip);
1189
1190 LLRegion t_region = LLRegion(nNoCovrPly, pfp);
1191
1192 // We do a test removal of the NoCovr region.
1193 // If the result iz empty, it must be that the NoCovr region is
1194 // the full extent M_COVR(CATCOV=2) feature found in NOAA ENCs.
1195 // We ignore it.
1196
1197 if (!t_region.Empty()) {
1198 LLRegion test_region = target_region;
1199 test_region.Subtract(t_region);
1200
1201 if (!test_region.Empty()) target_region = test_region;
1202 }
1203 }
1204 }
1205 }
1206
1207 // Another superbad hack....
1208 // Super small scale raster charts like bluemarble.kap usually cross the
1209 // prime meridian and Plypoints georef is problematic...... So, force full
1210 // screen coverage in the quilt
1211 // if( (cte.GetScale() > 90000000) && (cte.GetChartFamily() ==
1212 // CHART_FAMILY_RASTER) )
1213 // target_region = world_region;
1214
1215 return target_region;
1216}
1217
1218bool Quilt::BuildExtendedChartStackAndCandidateArray(int ref_db_index,
1219 ViewPort &vp_in) {
1220 double zoom_test_val = .002;
1221 zoom_test_val *= 2;
1222
1223 EmptyCandidateArray();
1224 m_extended_stack_array.clear();
1225
1226 int reference_scale = 1e8;
1227 int reference_type = -1;
1228 int reference_family = -1;
1229 int quilt_proj =
1230 m_bquiltanyproj ? vp_in.m_projection_type : PROJECTION_UNKNOWN;
1231
1232 if (ref_db_index >= 0) {
1233 const ChartTableEntry &cte_ref =
1234 ChartData->GetChartTableEntry(ref_db_index);
1235 reference_scale = cte_ref.GetScale();
1236 reference_type = cte_ref.GetChartType();
1237 if (!m_bquiltanyproj) quilt_proj = ChartData->GetDBChartProj(ref_db_index);
1238 reference_family = cte_ref.GetChartFamily();
1239 }
1240
1241 bool b_need_resort = false;
1242
1243 ViewPort vp_local = vp_in; // non-const copy
1244
1245 // if( !pCurrentStack ) {
1246 // pCurrentStack = new ChartStack;
1247 // ChartData->BuildChartStack( pCurrentStack, vp_local.clat,
1248 // vp_local.clon );
1249 // }
1250
1251 int n_charts = m_parent->GetpCurrentStack()->nEntry;
1252
1253 // Walk the current ChartStack...
1254 // Building the quilt candidate array
1255 for (int ics = 0; ics < n_charts; ics++) {
1256 int istack = m_parent->GetpCurrentStack()->GetDBIndex(ics);
1257 if (istack < 0) continue;
1258 m_extended_stack_array.push_back(istack);
1259
1260 // If the reference chart is cm93, we need not add any charts to the
1261 // candidate array from the vp center. All required charts will be added
1262 // (by family) as we consider the entire database (group) and full screen
1263 // later
1264 if (reference_type == CHART_TYPE_CM93COMP) continue;
1265
1266 const ChartTableEntry &cte = ChartData->GetChartTableEntry(istack);
1267
1268 // only charts of the proper projection and type may be quilted....
1269 // Also, only unskewed charts if so directed
1270 // and we avoid adding CM93 Composite until later
1271
1272 // If any PlugIn charts are involved, we make the inclusion test on chart
1273 // family, instead of chart type.
1274 if ((cte.GetChartType() == CHART_TYPE_PLUGIN) ||
1275 (reference_type == CHART_TYPE_PLUGIN)) {
1276 if (reference_family != cte.GetChartFamily()) {
1277 continue;
1278 }
1279 } else {
1280 if (reference_type != cte.GetChartType()) {
1281 continue;
1282 }
1283 }
1284
1285 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
1286
1287 // Also, check the scale of the proposed chart. If too small, skip it.
1288 int candidate_chart_scale = cte.GetScale();
1289 double chart_native_ppm =
1290 m_canvas_scale_factor / (double)candidate_chart_scale;
1291 double zoom_factor = vp_local.view_scale_ppm / chart_native_ppm;
1292 if (zoom_factor < zoom_test_val){
1293 m_extended_stack_array.pop_back();
1294 continue;
1295 }
1296
1297 double skew_norm = cte.GetChartSkew();
1298 if (skew_norm > 180.) skew_norm -= 360.;
1299
1300 if ((m_bquiltskew ? 1 : fabs(skew_norm) < 1.0) &&
1301 (m_bquiltanyproj || cte.GetChartProjectionType() == quilt_proj)) {
1302 QuiltCandidate *qcnew = new QuiltCandidate;
1303 qcnew->dbIndex = istack;
1304 qcnew->SetScale(cte.GetScale());
1305 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1306
1307 }
1308
1309 // if( ( reference_type == cte.GetChartType() ) ||
1310 // ( (cte.GetChartType() == CHART_TYPE_PLUGIN ) &&
1311 // (reference_family == cte.GetChartFamily() )) || (
1312 // (reference_type == CHART_TYPE_PLUGIN ) &&
1313 // (reference_family == cte.GetChartFamily() )) ){
1314 //
1315 // if( ( m_bquiltskew ? 1: fabs( skew_norm ) < 1.0 )
1316 // && ( m_bquiltanyproj || cte.GetChartProjectionType()
1317 // == quilt_proj )
1318 // && ( cte.GetChartType() != CHART_TYPE_CM93COMP ) ) {
1319 // QuiltCandidate *qcnew = new QuiltCandidate;
1320 // qcnew->dbIndex = i;
1321 // qcnew->ChartScale = cte.GetScale();
1322 //
1323 // m_pcandidate_array->push_back( qcnew ); //
1324 // auto-sorted on scale
1325 // }
1326 // }
1327 }
1328
1329 // Search the entire database, potentially adding all charts
1330 // which intersect the ViewPort in any way
1331 // .AND. other requirements.
1332 // Again, skipping cm93 for now
1333 int n_all_charts = ChartData->GetChartTableEntries();
1334
1335 LLBBox viewbox = vp_local.GetBBox();
1336 int sure_index = -1;
1337 int sure_index_scale = 0;
1338
1339 for (int i = 0; i < n_all_charts; i++) {
1340 // We can eliminate some charts immediately
1341 // Try to make these tests in some sensible order....
1342
1343 int groupIndex = m_parent->m_groupIndex;
1344 if ((groupIndex > 0) && (!ChartData->IsChartInGroup(i, groupIndex)))
1345 continue;
1346
1347 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
1348
1349 // On android, SDK > 29, we require that the directory of charts be "writable"
1350 // as determined by Android Java file system
1351#ifdef __OCPN__ANDROID__
1352 wxFileName fn(cte.GetFullSystemPath());
1353 if (!androidIsDirWritable( fn.GetPath()))
1354 continue;
1355#endif
1356
1357 if (reference_family != cte.GetChartFamily()) continue;
1358
1359 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
1360
1361 const LLBBox &chart_box = cte.GetBBox();
1362 if ((viewbox.IntersectOut(chart_box))) continue;
1363
1364 if (!m_bquiltanyproj && quilt_proj != cte.GetChartProjectionType())
1365 continue;
1366
1367 double skew_norm = cte.GetChartSkew();
1368 if (skew_norm > 180.) skew_norm -= 360.;
1369
1370 if (!m_bquiltskew && fabs(skew_norm) > 1.0) continue;
1371
1372 // Calculate zoom factor for this chart
1373 int candidate_chart_scale = cte.GetScale();
1374 double chart_native_ppm =
1375 m_canvas_scale_factor / (double)candidate_chart_scale;
1376 double zoom_factor = vp_in.view_scale_ppm / chart_native_ppm;
1377
1378 double zoom_factor_test = 0.2;
1379
1380 // Special case for S57 ENC
1381 // Add the chart only if the chart's fractional area exceeds n%
1382 if( CHART_TYPE_S57 == cte.GetChartType() ) {
1383 //Get the fractional area of this candidate
1384 double chart_area = (cte.GetLonMax() - cte.GetLonMin()) *
1385 (cte.GetLatMax() - cte.GetLatMin());
1386 double quilt_area = viewbox.GetLonRange() * viewbox.GetLatRange();
1387 if ((chart_area / quilt_area) < .01)
1388 continue;
1389 }
1390
1391 // Try to guarantee that there is one chart added with scale larger than
1392 // reference scale
1393 // Take note here, and keep track of the smallest scale chart that is
1394 // larger scale than reference....
1395 if (!cte.Scale_ge(reference_scale)) {
1396 if (cte.Scale_gt(sure_index_scale)) {
1397 sure_index = i;
1398 sure_index_scale = candidate_chart_scale;
1399 }
1400 }
1401
1402 // At this point, the candidate is the right type, skew, and projection,
1403 // and is on-screen somewhere.... Now add the candidate if its scale is
1404 // smaller than the reference scale, or is not excessively underzoomed.
1405
1406
1407 if ((cte.Scale_ge(reference_scale) && (zoom_factor > zoom_test_val)) || (zoom_factor > zoom_factor_test)) {
1408
1409 LLRegion cell_region = GetChartQuiltRegion(cte, vp_local);
1410
1411 // this is false if the chart has no actual overlap on screen
1412 // or lots of NoCovr regions. US3EC04.000 is a good example
1413 // i.e the full bboxes overlap, but the actual vp intersect is null.
1414 if (!cell_region.Empty()) {
1415 // Check to see if this chart is already in the stack array
1416 // by virtue of being under the Viewport center point....
1417 bool b_exists = false;
1418 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1419 if (i == m_extended_stack_array[ir]) {
1420 b_exists = true;
1421 break;
1422 }
1423 }
1424
1425 if (!b_exists) {
1426 // Check to be sure that this chart has not already been added
1427 // i.e. charts that have exactly the same file name and nearly the
1428 // same mod time These charts can be in the database due to having
1429 // the exact same chart in different directories, as may be desired
1430 // for some grouping schemes
1431 // Extended to also check for "identical" charts, having exact same EditionDate
1432 bool b_noadd = false;
1433 ChartTableEntry *pn = ChartData->GetpChartTableEntry(i);
1434 for (unsigned int id = 0; id < m_extended_stack_array.size(); id++) {
1435 if (m_extended_stack_array[id] != -1) {
1436 ChartTableEntry *pm =
1437 ChartData->GetpChartTableEntry(m_extended_stack_array[id]);
1438 bool bsameTime = false;
1439 if (pm->GetFileTime() && pn->GetFileTime()) {
1440 if (labs(pm->GetFileTime() - pn->GetFileTime()) < 60)
1441 bsameTime = true;
1442 }
1443 if (pm->GetChartEditionDate() == pn->GetChartEditionDate() )
1444 bsameTime = true;
1445
1446 if (bsameTime) {
1447 if (pn->GetpFileName()->IsSameAs(*(pm->GetpFileName())))
1448 b_noadd = true;
1449 }
1450 }
1451 }
1452
1453 if (!b_noadd) {
1454 m_extended_stack_array.push_back(i);
1455
1456 QuiltCandidate *qcnew = new QuiltCandidate;
1457 qcnew->dbIndex = i;
1458 qcnew->SetScale(
1459 candidate_chart_scale); // ChartData->GetDBChartScale( i );
1460
1461 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1462
1463 b_need_resort = true;
1464 }
1465 }
1466 }
1467 }
1468 } // for all charts
1469
1470 // Check to be sure that at least one chart was added that is larger scale
1471 // than reference scale
1472 if (-1 != sure_index) {
1473 // check to see if it is already in
1474 bool sure_exists = false;
1475 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1476 if (sure_index == m_extended_stack_array[ir]) {
1477 sure_exists = true;
1478 break;
1479 }
1480 }
1481
1482 // If not already added, do so now
1483 if (!sure_exists) {
1484 m_extended_stack_array.push_back(sure_index);
1485
1486 QuiltCandidate *qcnew = new QuiltCandidate;
1487 qcnew->dbIndex = sure_index;
1488 qcnew->SetScale(ChartData->GetDBChartScale(sure_index));
1489 m_pcandidate_array->push_back(qcnew); // auto-sorted on scale
1490
1491 b_need_resort = true;
1492 }
1493 }
1494
1495 // Re sort the extended stack array on scale
1496 if (b_need_resort && m_extended_stack_array.size() > 1) {
1497 std::sort(m_extended_stack_array.begin(), m_extended_stack_array.end(),
1498 CompareScalesStd);
1499 }
1500 return true;
1501}
1502
1503int Quilt::AdjustRefSelection(const ViewPort &vp_in) {
1504 // Starting from the currently selected Ref chart,
1505 // choose a ref chart that meets the required under/overzoom limits
1506 // It might be the same, so no change required
1507
1508 if (!ChartData) return false;
1509
1510 if (ChartData
1511 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1512 return false;
1513
1514 ViewPort vp_local = vp_in; // need a non-const copy
1515
1516 // As ChartdB data is always in rectilinear space, region calculations need
1517 // to be done with no VP rotation
1518 vp_local.SetRotationAngle(0.);
1519
1520 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1521
1522 ChartFamilyEnum family = CHART_FAMILY_RASTER;
1523 ChartTypeEnum type = CHART_TYPE_KAP;
1524
1525 // Get the desired family/type
1526 if (m_refchart_dbIndex >= 0) {
1527 const ChartTableEntry &cte_ref =
1528 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1529 type = (ChartTypeEnum)cte_ref.GetChartType();
1530 family = (ChartFamilyEnum)cte_ref.GetChartFamily();
1531 }
1532
1533 int ret_index = AdjustRefOnZoom(true, family, type, vp_in.chart_scale);
1534
1535 return ret_index;
1536}
1537
1538double Quilt::GetBestStartScale(int dbi_ref_hint, const ViewPort &vp_in) {
1539 if (!ChartData) return vp_in.view_scale_ppm;
1540
1541 if (ChartData
1542 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1543 return vp_in.view_scale_ppm;
1544
1545 ViewPort vp_local = vp_in; // need a non-const copy
1546
1547 // Validate Reference Chart hint
1548 int tentative_ref_index = dbi_ref_hint;
1549 if (dbi_ref_hint < 0) {
1550 // arbitrarily select reference chart as largest scale on current stack
1551 // if( !pCurrentStack ) {
1552 // pCurrentStack = new ChartStack;
1553 // ChartData->BuildChartStack( pCurrentStack, vp_local.clat,
1554 // vp_local.clon );
1555 // }
1556 tentative_ref_index = m_parent->GetpCurrentStack()->GetDBIndex(0);
1557 }
1558
1559 // As ChartdB data is always in rectilinear space, region calculations need
1560 // to be done with no VP rotation
1561 // double saved_vp_rotation = vp_local.rotation; // save
1562 // a copy
1563 vp_local.SetRotationAngle(0.);
1564
1565 BuildExtendedChartStackAndCandidateArray(tentative_ref_index, vp_local);
1566
1567 // tentative choice might not be in the extended stack....
1568 bool bf = false;
1569 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1570 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1571 if (qc->dbIndex == tentative_ref_index) {
1572 bf = true;
1573 break;
1574 }
1575 }
1576
1577 if (!bf && m_pcandidate_array->GetCount()) {
1578 tentative_ref_index = GetNewRefChart();
1579 BuildExtendedChartStackAndCandidateArray(tentative_ref_index, vp_local);
1580 }
1581
1582 double proposed_scale_onscreen = vp_in.chart_scale;
1583
1584 if (m_pcandidate_array->GetCount()) {
1585 m_refchart_dbIndex = tentative_ref_index;
1586 } else {
1587 // Need to choose some chart, find a quiltable candidate
1588 bool bfq = false;
1589 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1590 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1591 if (IsChartQuiltableRef(qc->dbIndex)) {
1592 m_refchart_dbIndex = qc->dbIndex;
1593 bfq = true;
1594 break;
1595 }
1596 }
1597
1598 if (!bfq) // fallback to first chart in stack
1599 m_refchart_dbIndex = m_parent->GetpCurrentStack()->GetDBIndex(0);
1600 }
1601
1602 if (m_refchart_dbIndex >= 0) {
1603 // Suggest a scale so that the largest scale candidate is "nominally"
1604 // scaled, meaning not overzoomed, and not underzoomed
1605 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
1606 if (pc) {
1607 double min_ref_scale =
1608 pc->GetNormalScaleMin(m_parent->GetCanvasScaleFactor(), false);
1609 double max_ref_scale = pc->GetNormalScaleMax(
1610 m_parent->GetCanvasScaleFactor(), m_canvas_width);
1611 if ((proposed_scale_onscreen >= min_ref_scale) &&
1612 (proposed_scale_onscreen <= max_ref_scale))
1613 return vp_in.view_scale_ppm;
1614 else {
1615 proposed_scale_onscreen = wxMin(proposed_scale_onscreen, max_ref_scale);
1616 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, min_ref_scale);
1617 }
1618 }
1619 }
1620 return m_parent->GetCanvasScaleFactor() / proposed_scale_onscreen;
1621}
1622
1623ChartBase *Quilt::GetRefChart() {
1624 if (m_refchart_dbIndex >= 0 && ChartData)
1625 return ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
1626 return nullptr;
1627}
1628
1629void Quilt::UnlockQuilt() {
1630 wxASSERT(m_bbusy == false);
1631 ChartData->UnLockCache();
1632 // unlocked only charts owned by the Quilt
1633 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1634 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1635 // if (pqc->b_locked == true)
1636 {
1637 ChartData->UnLockCacheChart(pqc->dbIndex);
1638 pqc->b_locked = false;
1639 }
1640 }
1641}
1642
1643bool Quilt::Compose(const ViewPort &vp_in) {
1644 if (!ChartData) return false;
1645
1646 if (ChartData
1647 ->IsBusy()) // This prevent recursion on chart loads that Yeild()
1648 return false;
1649
1650 if (!m_parent->GetpCurrentStack()) return false;
1651
1652 if (m_bbusy) return false;
1653
1654 // XXX call before setting m_bbusy for wxASSERT in UnlockQuilt
1655 UnlockQuilt();
1656 m_bbusy = true;
1657
1658 ViewPort vp_local = vp_in; // need a non-const copy
1659
1660 // Get Reference Chart parameters
1661 if (m_refchart_dbIndex >= 0) {
1662 const ChartTableEntry &cte_ref =
1663 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1664 m_reference_scale = cte_ref.GetScale();
1665 m_reference_type = cte_ref.GetChartType();
1666 if (!m_bquiltanyproj)
1667 m_quilt_proj = ChartData->GetDBChartProj(m_refchart_dbIndex);
1668 m_reference_family = cte_ref.GetChartFamily();
1669 }
1670
1671 // Set up the viewport projection type
1672 if (!m_bquiltanyproj) vp_local.SetProjectionType(m_quilt_proj);
1673
1674 // As ChartdB data is always in rectilinear space, region calculations need
1675 // to be done with no VP rotation
1676 // double saved_vp_rotation = vp_local.rotation; //
1677 // save a copy vp_local.SetRotationAngle( 0. );
1678
1679 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1680
1681 // It can happen (in groups switch, or single->quilt mode) that there
1682 // is no refchart known, but there are charts available in the piano.
1683 // Detect this case, and build the quilt based on the smallest scale chart
1684 // anywhere on screen.
1685
1686// if ((m_refchart_dbIndex < 0) && m_extended_stack_array.size()){
1687// // Take the smallest scale chart in the array.
1688// int tentative_dbIndex = m_extended_stack_array.back();
1689//
1690// // Verify that the zoom scale is acceptable.
1691// const ChartTableEntry &cte = ChartData->GetChartTableEntry(tentative_dbIndex);
1692// int candidate_chart_scale = cte.GetScale();
1693// double chart_native_ppm =
1694// m_canvas_scale_factor / (double)candidate_chart_scale;
1695// double zoom_factor = vp_local.view_scale_ppm / chart_native_ppm;
1696// if (zoom_factor > 0.1){
1697// m_refchart_dbIndex = tentative_dbIndex;
1698// BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1699// }
1700// }
1701
1702 // It is possible that the reference chart is not really part of the
1703 // visible quilt This can happen when the reference chart is panned
1704 // off-screen in full screen quilt mode
1705 // If this situation occurs, we need to immediately select a new reference
1706 // chart And rebuild the Candidate Array
1707 //
1708 // We also save the dbIndex of the "lost" chart, and try to recover it
1709 // on subsequent quilts, typically as the user pans the "lost" chart back
1710 // on-screen. The "lost" chart logic is reset on any zoom operations. See
1711 // FS#1221
1712 //
1713 // A special case occurs with cm93 composite chart set as the reference
1714 // chart: It is not at this point a candidate, so won't be found by the
1715 // search This case is indicated if the candidate count is zero. If so, do
1716 // not invalidate the ref chart
1717 bool bf = false;
1718 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
1719 QuiltCandidate *qc = m_pcandidate_array->Item(i);
1720 if (qc->dbIndex == m_refchart_dbIndex) {
1721 bf = true;
1722 break;
1723 }
1724 }
1725
1726 if (!bf && m_pcandidate_array->GetCount() &&
1727 (m_reference_type != CHART_TYPE_CM93COMP)) {
1728 m_lost_refchart_dbIndex = m_refchart_dbIndex; // save for later
1729 int candidate_ref_index = GetNewRefChart();
1730 if (m_refchart_dbIndex != candidate_ref_index) {
1731 m_refchart_dbIndex = candidate_ref_index;
1732 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1733 }
1734 // There was no viable candidate of smaller scale than the "lost
1735 // chart", so choose the smallest scale chart in the candidate list.
1736 else {
1737 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1738 if (m_pcandidate_array->GetCount()) {
1739 m_refchart_dbIndex =
1740 m_pcandidate_array->Item(m_pcandidate_array->GetCount() - 1)
1741 ->dbIndex;
1742 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1743 }
1744 }
1745 }
1746
1747 if ((-1 != m_lost_refchart_dbIndex) &&
1748 (m_lost_refchart_dbIndex != m_refchart_dbIndex)) {
1749 // Is the lost chart in the extended stack ?
1750 // If so, build a new Cnadidate array based upon the lost chart
1751 for (unsigned int ir = 0; ir < m_extended_stack_array.size(); ir++) {
1752 if (m_lost_refchart_dbIndex == m_extended_stack_array[ir]) {
1753 m_refchart_dbIndex = m_lost_refchart_dbIndex;
1754 BuildExtendedChartStackAndCandidateArray(m_refchart_dbIndex, vp_local);
1755 m_lost_refchart_dbIndex = -1;
1756 break;
1757 }
1758 }
1759 }
1760
1761 bool b_has_overlays = false;
1762
1763 // If this is an S57 quilt, we need to know if there are overlays in it
1764 if (CHART_FAMILY_VECTOR == m_reference_family) {
1765 for (unsigned int ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1766 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1767 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1768
1769 if (s57chart::IsCellOverlayType(cte.GetFullSystemPath())) {
1770 b_has_overlays = true;
1771 break;
1772 ;
1773 }
1774 }
1775 }
1776
1777 // Using Region logic, and starting from the largest scale chart
1778 // figuratively "draw" charts until the ViewPort window is completely
1779 // quilted over Add only those charts whose scale is smaller than the
1780 // "reference scale"
1781// const LLRegion cvp_region = vp_local.GetLLRegion(
1782// wxRect(0, 0, vp_local.pix_width, vp_local.pix_height));
1783 const LLRegion cvp_region = vp_local.GetLLRegion(
1784 vp_local.rv_rect);
1785 LLRegion vp_region = cvp_region;
1786 unsigned int ir;
1787
1788 // "Draw" the reference chart first, since it is special in that it
1789 // controls the fine vpscale setting
1790 QuiltCandidate *pqc_ref = NULL;
1791 for (ir = 0; ir < m_pcandidate_array->GetCount();
1792 ir++) // find ref chart entry
1793 {
1794 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1795 if (pqc->dbIndex == m_refchart_dbIndex) {
1796 pqc_ref = pqc;
1797 break;
1798 }
1799 }
1800
1801 // Quilted regions can be simplified to reduce the cost of region operations,
1802 // in this case allow a maximum error of 8 pixels (the rendered display is
1803 // much better, this is only for composing the quilt)
1804 const double z = 111274.96299695622;
1806 double factor = 8.0 / (vp_local.view_scale_ppm * z);
1807
1808 if (pqc_ref) {
1809 const ChartTableEntry &cte_ref =
1810 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1811
1812 LLRegion vpu_region(cvp_region);
1813
1814 // LLRegion chart_region = pqc_ref->GetCandidateRegion();
1815 LLRegion &chart_region = pqc_ref->GetReducedCandidateRegion(factor);
1816
1817 if (cte_ref.GetChartType() != CHART_TYPE_MBTILES) {
1818 if (!chart_region.Empty()) {
1819 vpu_region.Intersect(chart_region);
1820
1821 if (vpu_region.Empty())
1822 pqc_ref->b_include = false; // skip this chart, no true overlap
1823 else {
1824 pqc_ref->b_include = true;
1825 vp_region.Subtract(chart_region); // adding this chart
1826 }
1827 } else
1828 pqc_ref->b_include = false; // skip this chart, empty region
1829 } else {
1830 pqc_ref->b_include = false; // skip this chart, mbtiles
1831 }
1832 }
1833
1834 // Now the rest of the candidates
1835 if (!vp_region.Empty()) {
1836 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1837 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1838
1839 if (pqc->dbIndex == m_refchart_dbIndex) continue; // already did this one
1840
1841 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1842
1843 // Skip overlays on this pass, so that they do not subtract from quilt
1844 // and thus displace a geographical cell with the same extents. Overlays
1845 // will be picked up in the next pass, if any are found
1846 if (CHART_FAMILY_VECTOR == m_reference_family) {
1847 if (s57chart::IsCellOverlayType(cte.GetFullSystemPath())) {
1848 continue;
1849 }
1850 }
1851
1852 // Skip MBTiles
1853 if (CHART_TYPE_MBTILES == cte.GetChartType()) {
1854 pqc->b_include = false; // skip this chart, mbtiles
1855 continue;
1856 }
1857
1858 if (cte.Scale_ge(m_reference_scale)) {
1859 // If this chart appears in the no-show array, then simply include it,
1860 // but don't subtract its region when determining the smaller scale
1861 // charts to include.....
1862 bool b_in_noshow = false;
1863 for (unsigned int ins = 0;
1864 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
1865 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
1866 pqc->dbIndex) // chart is in the noshow list
1867 {
1868 b_in_noshow = true;
1869 break;
1870 }
1871 }
1872
1873 if (!b_in_noshow) {
1874 // Check intersection
1875 LLRegion vpu_region(cvp_region);
1876
1877 // LLRegion chart_region = pqc->GetCandidateRegion( ); //quilt_region;
1878 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
1879
1880 if (!chart_region.Empty()) {
1881 vpu_region.Intersect(chart_region);
1882
1883 if (vpu_region.Empty())
1884 pqc->b_include = false; // skip this chart, no true overlap
1885 else {
1886 pqc->b_include = true;
1887 vp_region.Subtract(chart_region); // adding this chart
1888 }
1889 } else
1890 pqc->b_include = false; // skip this chart, empty region
1891 } else {
1892 pqc->b_include = true;
1893 }
1894
1895 } else {
1896 pqc->b_include = false; // skip this chart, scale is too large
1897 }
1898
1899 if (vp_region.Empty()) // normal stop condition, quilt is full
1900 break;
1901 }
1902 }
1903
1904 // For S57 quilts, walk the list again to identify overlay cells found
1905 // previously, and make sure they are always included and not eclipsed
1906 if (b_has_overlays && (CHART_FAMILY_VECTOR == m_reference_family)) {
1907 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1908 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1909
1910 if (pqc->dbIndex == m_refchart_dbIndex) continue; // already did this one
1911
1912 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1913
1914 if (cte.Scale_ge(m_reference_scale)) {
1915 bool b_in_noshow = false;
1916 for (unsigned int ins = 0;
1917 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
1918 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
1919 pqc->dbIndex) // chart is in the noshow list
1920 {
1921 b_in_noshow = true;
1922 break;
1923 }
1924 }
1925
1926 if (!b_in_noshow) {
1927 // Check intersection
1928 LLRegion vpu_region(cvp_region);
1929
1930 // LLRegion chart_region = pqc->GetCandidateRegion( );
1931 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
1932
1933 if (!chart_region.Empty()) 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 bool b_overlay =
1939 s57chart::IsCellOverlayType(cte.GetFullSystemPath());
1940 if (b_overlay) pqc->b_include = true;
1941 }
1942
1943 // If the reference chart happens to be an overlay (e.g.
1944 // 3UABUOYS.000), we dont want it to eclipse any smaller scale
1945 // standard useage charts.
1946 const ChartTableEntry &cte_ref =
1947 ChartData->GetChartTableEntry(m_refchart_dbIndex);
1948 if (s57chart::IsCellOverlayType(cte_ref.GetFullSystemPath())) {
1949 pqc->b_include = true;
1950 }
1951 }
1952 }
1953 }
1954 }
1955
1956 // Walk the candidate list again, marking "eclipsed" charts
1957 // which at this point are the ones with b_include == false .AND. whose
1958 // scale is strictly smaller than the ref scale Also, maintain the member
1959 // list of same
1960
1961 m_eclipsed_stack_array.clear();
1962
1963 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
1964 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
1965
1966 if (!pqc->b_include) {
1967 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
1968 if (cte.Scale_ge(m_reference_scale) &&
1969 (cte.GetChartType() != CHART_TYPE_MBTILES)) {
1970 m_eclipsed_stack_array.push_back(pqc->dbIndex);
1971 pqc->b_eclipsed = true;
1972 }
1973 }
1974 }
1975
1976 // Potentially add cm93 to the candidate array if the region is not yet
1977 // fully covered
1978 if (((m_bquiltanyproj || m_quilt_proj == PROJECTION_MERCATOR)) &&
1979 !vp_region.Empty()) {
1980 bool b_must_add_cm93 = true;
1981#if 0
1982 // Check the remaining unpainted region.
1983 // It may contain very small "slivers" of empty space, due to mixing of very small scale charts
1984 // with the quilt. If this is the case, do not waste time loading cm93....
1985
1986 OCPNRegionIterator updd( vp_region );
1987 while( updd .HaveRects()) {
1988 wxRect rect = updd.GetRect();
1989 if( ( rect.width > 2 ) && ( rect.height > 2 ) ) {
1990 b_must_add_cm93 = true;
1991 break;
1992 }
1993 updd.NextRect();
1994 }
1995#endif
1996
1997 if (b_must_add_cm93) {
1998 for (int ics = 0; ics < m_parent->GetpCurrentStack()->nEntry; ics++) {
1999 int i = m_parent->GetpCurrentStack()->GetDBIndex(ics);
2000 if (CHART_TYPE_CM93COMP == ChartData->GetDBChartType(i)) {
2001 QuiltCandidate *qcnew = new QuiltCandidate;
2002 qcnew->dbIndex = i;
2003 qcnew->SetScale(ChartData->GetDBChartScale(i));
2004
2005 m_pcandidate_array->Add(qcnew);
2006 }
2007 }
2008 }
2009 }
2010
2011 // Check the list...if no charts are visible due to all being smaller than
2012 // reference_scale, then make sure the smallest scale chart which has any
2013 // true region intersection is visible anyway Also enable any other charts
2014 // which are the same scale as the first one added
2015 bool b_vis = false;
2016 for (unsigned int i = 0; i < m_pcandidate_array->GetCount(); i++) {
2017 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2018 if (pqc->b_include) {
2019 b_vis = true;
2020 break;
2021 }
2022 }
2023
2024 if (!b_vis && m_pcandidate_array->GetCount()) {
2025 int add_scale = 0;
2026
2027 for (int i = m_pcandidate_array->GetCount() - 1; i >= 0; i--) {
2028 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2029 const ChartTableEntry &cte = ChartData->GetChartTableEntry(pqc->dbIndex);
2030
2031 // Don't add cm93 yet, it is always covering the quilt...
2032 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
2033
2034 // Don't add MBTiles
2035 if (cte.GetChartType() == CHART_TYPE_MBTILES) continue;
2036
2037 // Check intersection
2038 LLRegion vpck_region(vp_local.GetBBox());
2039
2040 // LLRegion chart_region = pqc->GetCandidateRegion();
2041 LLRegion &chart_region = pqc->GetReducedCandidateRegion(factor);
2042
2043 if (!chart_region.Empty()) vpck_region.Intersect(chart_region);
2044
2045 if (!vpck_region.Empty()) {
2046 if (add_scale) {
2047 if (cte.Scale_eq(add_scale)) {
2048 pqc->b_include = true;
2049 }
2050 } else {
2051 pqc->b_include = true;
2052 add_scale = cte.GetScale();
2053 }
2054 }
2055 }
2056 }
2057
2058 // Finally, build a list of "patches" for the quilt.
2059 // Smallest scale first, as this will be the natural drawing order
2060
2061 m_PatchList.DeleteContents(true);
2062 m_PatchList.Clear();
2063
2064 if (m_pcandidate_array->GetCount()) {
2065 for (int i = m_pcandidate_array->GetCount() - 1; i >= 0; i--) {
2066 QuiltCandidate *pqc = m_pcandidate_array->Item(i);
2067
2068 // cm93 add has been deferred until here
2069 // so that it would not displace possible raster or ENCs of larger
2070 // scale
2071 const ChartTableEntry &m = ChartData->GetChartTableEntry(pqc->dbIndex);
2072
2073 if (m.GetChartType() == CHART_TYPE_CM93COMP)
2074 pqc->b_include = true; // force acceptance of this chart in quilt
2075 // would not be in candidate array if not elected
2076
2077 if (pqc->b_include) {
2078 QuiltPatch *pqp = new QuiltPatch;
2079 pqp->dbIndex = pqc->dbIndex;
2080 pqp->ProjType = m.GetChartProjectionType();
2081 // this is the region used for drawing, don't reduce it
2082 // it's visible
2083 pqp->quilt_region = pqc->GetCandidateRegion();
2084 // pqp->quilt_region = pqc->GetReducedCandidateRegion(factor);
2085
2086 pqp->b_Valid = true;
2087
2088 m_PatchList.Append(pqp);
2089 }
2090 }
2091 }
2092 // From here on out, the PatchList is usable...
2093
2094#ifdef QUILT_TYPE_1
2095 if (!m_bquiltanyproj) {
2096 // Establish the quilt projection type
2097 m_quilt_proj = PROJECTION_MERCATOR; // default
2098 ChartBase *ppc = GetLargestScaleChart();
2099 if (ppc) m_quilt_proj = ppc->GetChartProjectionType();
2100 }
2101#endif
2102
2103 if (!m_bquiltanyproj) {
2104 // Walk the PatchList, marking any entries whose projection does not
2105 // match the determined quilt projection
2106 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2107 wxPatchListNode *pcinode = m_PatchList.Item(i);
2108 QuiltPatch *piqp = pcinode->GetData();
2109 if ((piqp->ProjType != m_quilt_proj) &&
2110 (piqp->ProjType != PROJECTION_UNKNOWN))
2111 piqp->b_Valid = false;
2112 }
2113 }
2114
2115 // Walk the PatchList, marking any entries which appear in the noshow array
2116 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2117 wxPatchListNode *pcinode = m_PatchList.Item(i);
2118 QuiltPatch *piqp = pcinode->GetData();
2119 for (unsigned int ins = 0;
2120 ins < m_parent->GetQuiltNoshowIindexArray().size(); ins++) {
2121 if (m_parent->GetQuiltNoshowIindexArray()[ins] ==
2122 piqp->dbIndex) // chart is in the noshow list
2123 {
2124 piqp->b_Valid = false;
2125 break;
2126 }
2127 }
2128 }
2129
2130 // Generate the final render regions for the patches, one by one
2131
2132 m_covered_region.Clear();
2133#if 1 // this does the same as before with a lot less operations if there are
2134 // many charts
2135
2136 // If the reference chart is cm93, we need to render it first.
2137 bool b_skipCM93 = false;
2138 if (m_reference_type == CHART_TYPE_CM93COMP) {
2139 // find cm93 in the list
2140 for (int i = m_PatchList.GetCount() - 1; i >= 0; i--) {
2141 wxPatchListNode *pcinode = m_PatchList.Item(i);
2142 QuiltPatch *piqp = pcinode->GetData();
2143 if (!piqp->b_Valid) // skip invalid entries
2144 continue;
2145
2146 const ChartTableEntry &m = ChartData->GetChartTableEntry(piqp->dbIndex);
2147
2148 if (m.GetChartType() == CHART_TYPE_CM93COMP) {
2149 // Start with the chart's full region coverage.
2150 piqp->ActiveRegion = piqp->quilt_region;
2151 piqp->ActiveRegion.Intersect(cvp_region);
2152
2153 // Update the next pass full region to remove the region just
2154 // allocated
2155 m_covered_region.Union(piqp->quilt_region);
2156
2157 b_skipCM93 = true; // did this already...
2158 break;
2159 }
2160 }
2161 }
2162
2163 // Proceeding from largest scale to smallest....
2164
2165 for (int i = m_PatchList.GetCount() - 1; i >= 0; i--) {
2166 wxPatchListNode *pcinode = m_PatchList.Item(i);
2167 QuiltPatch *piqp = pcinode->GetData();
2168 if (!piqp->b_Valid) // skip invalid entries
2169 continue;
2170
2171 const ChartTableEntry &cte = ChartData->GetChartTableEntry(piqp->dbIndex);
2172
2173 if (b_skipCM93) {
2174 if (cte.GetChartType() == CHART_TYPE_CM93COMP) continue;
2175 }
2176
2177 // Start with the chart's full region coverage.
2178 piqp->ActiveRegion = piqp->quilt_region;
2179
2180 // this operation becomes expensive with lots of charts
2181 if (!b_has_overlays && m_PatchList.GetCount() < 25)
2182 piqp->ActiveRegion.Subtract(m_covered_region);
2183
2184 piqp->ActiveRegion.Intersect(cvp_region);
2185
2186 // Could happen that a larger scale chart covers completely a smaller
2187 // scale chart
2188 if (piqp->ActiveRegion.Empty() && (piqp->dbIndex != m_refchart_dbIndex))
2189 piqp->b_eclipsed = true;
2190
2191 // Maintain the present full quilt coverage region
2192 piqp->b_overlay = false;
2193 if (cte.GetChartFamily() == CHART_FAMILY_VECTOR) {
2194 piqp->b_overlay = s57chart::IsCellOverlayType(cte.GetFullSystemPath());
2195 }
2196
2197 if (!piqp->b_overlay) m_covered_region.Union(piqp->quilt_region);
2198 }
2199#else
2200 // this is the old algorithm does the same thing in n^2/2 operations instead
2201 // of 2*n-1
2202 for (unsigned int i = 0; i < m_PatchList.GetCount(); i++) {
2203 wxPatchListNode *pcinode = m_PatchList.Item(i);
2204 QuiltPatch *piqp = pcinode->GetData();
2205
2206 if (!piqp->b_Valid) // skip invalid entries
2207 continue;
2208
2209 const ChartTableEntry &ctei = ChartData->GetChartTableEntry(piqp->dbIndex);
2210
2211 // Start with the chart's full region coverage.
2212 LLRegion vpr_region = piqp->quilt_region;
2213
2214 // This clause should be moved into the rendering routine for quilts so that
2215 // the actual region logic need only be applied to the render region
2216#if 1 // This clause went away with full-screen quilting
2217 // ...and came back with OpenGL....
2218
2219 // fetch and subtract regions for all larger scale charts
2220 for (unsigned int k = i + 1; k < m_PatchList.GetCount(); k++) {
2221 wxPatchListNode *pnode = m_PatchList.Item(k);
2222 QuiltPatch *pqp = pnode->GetData();
2223
2224 if (!pqp->b_Valid) // skip invalid entries
2225 continue;
2226
2235
2237
2241 // if( ( CHART_TYPE_S57 != ctei.GetChartType() ))
2242 if (!b_has_overlays) {
2243 if (!vpr_region.Empty()) {
2244 const ChartTableEntry &cte =
2245 ChartData->GetChartTableEntry(pqp->dbIndex);
2246 LLRegion larger_scale_chart_region =
2247 pqp->quilt_region; // GetChartQuiltRegion( cte, vp_local );
2248
2249 vpr_region.Subtract(larger_scale_chart_region);
2250 }
2251 }
2252 }
2253#endif
2254
2255 // Whatever is left in the vpr region and has not been yet rendered must
2256 // belong to the current target chart
2257
2258 wxPatchListNode *pinode = m_PatchList.Item(i);
2259 QuiltPatch *pqpi = pinode->GetData();
2260 pqpi->ActiveRegion = vpr_region;
2261
2262 // Move the active region so that upper left is 0,0 in final render
2263 // region
2264 // pqpi->ActiveRegion.Offset( -vp_local.rv_rect.x,
2265 // -vp_local.rv_rect.y );
2266
2267 // Could happen that a larger scale chart covers completely a smaller
2268 // scale chart
2269 if (pqpi->ActiveRegion.Empty()) pqpi->b_eclipsed = true;
2270
2271 // Update the next pass full region to remove the region just allocated
2272 // if( !vpr_region.Empty() )
2273 // unrendered_region.Subtract( vpr_region );
2274
2275 // Maintain the present full quilt coverage region
2276 // if( !pqpi->ActiveRegion.Empty() )
2277 m_covered_region.Union(pqpi->ActiveRegion);
2278 }
2279#endif
2280 // Restore temporary VP Rotation
2281 // vp_local.SetRotationAngle( saved_vp_rotation );
2282
2283 // Walk the list again, removing any entries marked as eclipsed....
2284 unsigned int il = 0;
2285 while (il < m_PatchList.GetCount()) {
2286 wxPatchListNode *pcinode = m_PatchList.Item(il);
2287 QuiltPatch *piqp = pcinode->GetData();
2288 if (piqp->b_eclipsed) {
2289 // Make sure that this chart appears in the eclipsed list...
2290 // This can happen when....
2291 bool b_noadd = false;
2292 for (unsigned int ir = 0; ir < m_eclipsed_stack_array.size(); ir++) {
2293 if (piqp->dbIndex == m_eclipsed_stack_array[ir]) {
2294 b_noadd = true;
2295 break;
2296 }
2297 }
2298 if (!b_noadd) m_eclipsed_stack_array.push_back(piqp->dbIndex);
2299
2300 m_PatchList.DeleteNode(pcinode);
2301 il = 0; // restart the list walk
2302 }
2303
2304 else
2305 il++;
2306 }
2307 // Mark the quilt to indicate need for background clear if the region is
2308 // not fully covered
2309 // m_bneed_clear = !unrendered_region.Empty();
2310 // m_back_region = unrendered_region;
2311
2312 // Finally, iterate thru the quilt and preload all of the required charts.
2313 // For dynamic S57 SENC creation, this is where SENC creation happens
2314 // first.....
2315
2316 // Stop (temporarily) canvas paint events, since some chart loads mught
2317 // Yield(), thus causing performance loss on recursion We will (always??) get
2318 // a refresh on the new Quilt anyway...
2319 m_parent->EnablePaint(false);
2320
2321 // first lock charts already in the cache
2322 // otherwise under memory pressure if chart1 and chart2
2323 // are in the quilt loading chart1 could evict chart2
2324 //
2325 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2326 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2327 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2328 if (ChartData->IsChartLocked(pqc->dbIndex)) // already locked
2329 pqc->b_locked = true;
2330 else
2331 pqc->b_locked = ChartData->LockCacheChart(pqc->dbIndex);
2332 }
2333 }
2334
2335 // open charts not in the cache
2336 for (ir = 0; ir < m_pcandidate_array->GetCount(); ir++) {
2337 QuiltCandidate *pqc = m_pcandidate_array->Item(ir);
2338 if ((pqc->b_include) && (!pqc->b_eclipsed)) {
2339 // I am fairly certain this test can now be removed
2340 // with improved smooth movement logic
2341 // if( !ChartData->IsChartInCache( pqc->dbIndex ) )
2342 // b_stop_movement = true;
2343 // only lock chart if not already locked
2344 if (ChartData->OpenChartFromDBAndLock(pqc->dbIndex, FULL_INIT,
2345 !pqc->b_locked))
2346 pqc->b_locked = true;
2347 }
2348 }
2349
2350 m_parent->EnablePaint(true);
2351 // Build and maintain the array of indexes in this quilt
2352
2353 m_last_index_array = m_index_array; // save the last one for delta checks
2354
2355 m_index_array.clear();
2356
2357 // The index array is to be built in reverse, largest scale first
2358 unsigned int kl = m_PatchList.GetCount();
2359 for (unsigned int k = 0; k < kl; k++) {
2360 wxPatchListNode *cnode = m_PatchList.Item((kl - k) - 1);
2361 m_index_array.push_back(cnode->GetData()->dbIndex);
2362 cnode = cnode->GetNext();
2363 }
2364
2365 // Walk the patch list again, checking the depth units
2366 // If they are all the same, then the value is usable
2367
2368 m_quilt_depth_unit = _T("");
2369 ChartBase *pc = ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
2370 if (pc) {
2371 m_quilt_depth_unit = pc->GetDepthUnits();
2372
2373 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2374 int units = ps52plib->m_nDepthUnitDisplay;
2375 switch (units) {
2376 case 0:
2377 m_quilt_depth_unit = _T("Feet");
2378 break;
2379 case 1:
2380 m_quilt_depth_unit = _T("Meters");
2381 break;
2382 case 2:
2383 m_quilt_depth_unit = _T("Fathoms");
2384 break;
2385 }
2386 }
2387 }
2388
2389 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2390 wxPatchListNode *pnode = m_PatchList.Item(k);
2391 QuiltPatch *pqp = pnode->GetData();
2392
2393 if (!pqp->b_Valid) // skip invalid entries
2394 continue;
2395
2396 ChartBase *pc = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2397 if (pc) {
2398 wxString du = pc->GetDepthUnits();
2399 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2400 int units = ps52plib->m_nDepthUnitDisplay;
2401 switch (units) {
2402 case 0:
2403 du = _T("Feet");
2404 break;
2405 case 1:
2406 du = _T("Meters");
2407 break;
2408 case 2:
2409 du = _T("Fathoms");
2410 break;
2411 }
2412 }
2413 wxString dul = du.Lower();
2414 wxString ml = m_quilt_depth_unit.Lower();
2415
2416 if (dul != ml) {
2417 // Try all the odd cases
2418 if (dul.StartsWith(_T("meters")) && ml.StartsWith(_T("meters")))
2419 continue;
2420 else if (dul.StartsWith(_T("metres")) && ml.StartsWith(_T("metres")))
2421 continue;
2422 else if (dul.StartsWith(_T("fathoms")) && ml.StartsWith(_T("fathoms")))
2423 continue;
2424 else if (dul.StartsWith(_T("met")) && ml.StartsWith(_T("met")))
2425 continue;
2426
2427 // They really are different
2428 m_quilt_depth_unit = _T("");
2429 break;
2430 }
2431 }
2432 }
2433
2434 // And try to prove that all required charts are in the cache
2435 // If one is missing, try to load it
2436 // If still missing, remove its patch from the quilt
2437 // This will probably leave a "black hole" in the quilt...
2438 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2439 wxPatchListNode *pnode = m_PatchList.Item(k);
2440 QuiltPatch *pqp = pnode->GetData();
2441
2442 if (pqp->b_Valid) {
2443 if (!ChartData->IsChartInCache(pqp->dbIndex)) {
2444 wxLogMessage(_T(" Quilt Compose cache miss..."));
2445 ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2446 if (!ChartData->IsChartInCache(pqp->dbIndex)) {
2447 wxLogMessage(_T(" Oops, removing from quilt..."));
2448 pqp->b_Valid = false;
2449 }
2450 }
2451 }
2452 }
2453
2454 // Make sure the reference chart is in the cache
2455 if (!ChartData->IsChartInCache(m_refchart_dbIndex))
2456 ChartData->OpenChartFromDB(m_refchart_dbIndex, FULL_INIT);
2457
2458 // Walk the patch list again, checking the error factor
2459 // Also, directly mark the patch to indicate if it should be treated as an
2460 // overlay as seen in Austrian Inland series
2461
2462 m_bquilt_has_overlays = false;
2463 m_max_error_factor = 0.;
2464 for (unsigned int k = 0; k < m_PatchList.GetCount(); k++) {
2465 wxPatchListNode *pnode = m_PatchList.Item(k);
2466 QuiltPatch *pqp = pnode->GetData();
2467
2468 if (!pqp->b_Valid) // skip invalid entries
2469 continue;
2470
2471 ChartBase *pc = ChartData->OpenChartFromDB(pqp->dbIndex, FULL_INIT);
2472 if (pc) {
2473 m_max_error_factor =
2474 wxMax(m_max_error_factor, pc->GetChart_Error_Factor());
2475 if (pc->GetChartFamily() == CHART_FAMILY_VECTOR) {
2476 bool isOverlay = IsChartS57Overlay(pqp->dbIndex);
2477 pqp->b_overlay = isOverlay;
2478 if (isOverlay) m_bquilt_has_overlays = true;
2479 }
2480 }
2481 }
2482
2483 m_bcomposed = true;
2484
2485 m_vp_quilt = vp_in; // save the corresponding ViewPort locally
2486
2487 ChartData->LockCache();
2488
2489 // Create and store a hash value representing the contents of the
2490 // m_extended_stack_array
2491 unsigned long xa_hash = 5381;
2492 for (unsigned int im = 0; im < m_extended_stack_array.size(); im++) {
2493 int dbindex = m_extended_stack_array[im];
2494 xa_hash = ((xa_hash << 5) + xa_hash) + dbindex; /* hash * 33 + dbindex */
2495 }
2496
2497 m_xa_hash = xa_hash;
2498
2499 m_bbusy = false;
2500 return true;
2501}
2502
2503// Compute and update the member quilt render region, considering all scale
2504// factors, group exclusions, etc.
2505void Quilt::ComputeRenderRegion(ViewPort &vp, OCPNRegion &chart_region) {
2506 if (!m_bcomposed) return;
2507
2508 OCPNRegion rendered_region;
2509
2510 if (GetnCharts() && !m_bbusy && !chart_region.Empty()) {
2511 // Walk the quilt, considering each chart from smallest scale to largest
2512
2513 ChartBase *chart = GetFirstChart();
2514
2515 while (chart) {
2516 if (!(chart->GetChartProjectionType() != PROJECTION_MERCATOR &&
2517 vp.b_MercatorProjectionOverride)) {
2518 QuiltPatch *pqp = GetCurrentPatch();
2519 if (pqp->b_Valid) {
2520 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2521 chart_region, pqp->ActiveRegion, chart->GetNativeScale());
2522 if (!get_screen_region.Empty())
2523 rendered_region.Union(get_screen_region);
2524 }
2525 }
2526 chart = GetNextChart();
2527 }
2528 }
2529 // Record the region actually rendered
2530 m_rendered_region = rendered_region;
2531}
2532
2533int g_render;
2534
2535bool Quilt::RenderQuiltRegionViewOnDCNoText(wxMemoryDC &dc, ViewPort &vp,
2536 OCPNRegion &chart_region) {
2537 return DoRenderQuiltRegionViewOnDC(dc, vp, chart_region);
2538}
2539
2540bool Quilt::RenderQuiltRegionViewOnDCTextOnly(wxMemoryDC &dc, ViewPort &vp,
2541 OCPNRegion &chart_region) {
2542 return DoRenderQuiltRegionViewOnDCTextOnly(dc, vp, chart_region);
2543}
2544
2545bool Quilt::DoRenderQuiltRegionViewOnDC(wxMemoryDC &dc, ViewPort &vp,
2546 OCPNRegion &chart_region) {
2547#ifdef ocpnUSE_DIBSECTION
2548 ocpnMemDC tmp_dc;
2549#else
2550 wxMemoryDC tmp_dc;
2551#endif
2552
2553 if (!m_bcomposed) return false;
2554
2555 OCPNRegion rendered_region;
2556
2557 if (GetnCharts() && !m_bbusy) {
2558 OCPNRegion screen_region = chart_region;
2559
2560 // Walk the quilt, drawing each chart from smallest scale to largest
2561 // Render the quilt's charts onto a temp dc
2562 // and blit the active region rectangles to to target dc, one-by-one
2563
2564 ChartBase *chart = GetFirstChart();
2565 int chartsDrawn = 0;
2566
2567 if (!chart_region.Empty()) {
2568 while (chart) {
2569 bool okToRender = true;
2570
2571 if (chart->GetChartProjectionType() != PROJECTION_MERCATOR &&
2572 vp.b_MercatorProjectionOverride)
2573 okToRender = false;
2574
2575 if (!okToRender) {
2576 chart = GetNextChart();
2577 continue;
2578 }
2579 QuiltPatch *pqp = GetCurrentPatch();
2580 if (pqp->b_Valid) {
2581 bool b_chart_rendered = false;
2582 LLRegion get_region = pqp->ActiveRegion;
2583
2584 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2585 chart_region, get_region, chart->GetNativeScale());
2586 if (!get_screen_region.Empty()) {
2587 if (!pqp->b_overlay) {
2588 if (chart->GetChartType() == CHART_TYPE_CM93COMP) {
2589 b_chart_rendered =
2590 chart->RenderRegionViewOnDC(tmp_dc, vp, get_screen_region);
2591 } else {
2592 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2593 if (Chs57) {
2594 if (Chs57->m_RAZBuilt) {
2595 b_chart_rendered = Chs57->RenderRegionViewOnDCNoText(
2596 tmp_dc, vp, get_screen_region);
2597 }
2598 } else {
2599 ChartPlugInWrapper *ChPI =
2600 dynamic_cast<ChartPlugInWrapper *>(chart);
2601 if (ChPI) {
2602 b_chart_rendered = ChPI->RenderRegionViewOnDCNoText(
2603 tmp_dc, vp, get_screen_region);
2604 } else
2605 b_chart_rendered = chart->RenderRegionViewOnDC(
2606 tmp_dc, vp, get_screen_region);
2607
2608 b_chart_rendered = true;
2609 }
2610 }
2611
2612 // if( chart->GetChartType() !=
2613 // CHART_TYPE_CM93COMP )
2614 // b_chart_rendered = true;
2615 screen_region.Subtract(get_screen_region);
2616 }
2617 }
2618
2619 OCPNRegionIterator upd(get_screen_region);
2620 while (upd.HaveRects()) {
2621 wxRect rect = upd.GetRect();
2622 dc.Blit(rect.x, rect.y, rect.width, rect.height, &tmp_dc, rect.x,
2623 rect.y, wxCOPY, true);
2624 upd.NextRect();
2625 }
2626
2627 tmp_dc.SelectObject(wxNullBitmap);
2628
2629 if (b_chart_rendered) rendered_region.Union(get_screen_region);
2630 }
2631
2632 chartsDrawn++;
2633 chart = GetNextChart();
2634 }
2635 }
2636
2637 if (!chartsDrawn) m_parent->GetVP().SetProjectionType(PROJECTION_MERCATOR);
2638
2639 // Render any Overlay patches for s57 charts(cells)
2640 if (m_bquilt_has_overlays && !chart_region.Empty()) {
2641 chart = GetFirstChart();
2642 while (chart) {
2643 QuiltPatch *pqp = GetCurrentPatch();
2644 if (pqp->b_Valid) {
2645 if (pqp->b_overlay) {
2646 LLRegion get_region = pqp->ActiveRegion;
2647 OCPNRegion get_screen_region = vp.GetVPRegionIntersect(
2648 chart_region, get_region, chart->GetNativeScale());
2649 if (!get_region.Empty()) {
2650 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2651 if (Chs57) {
2652 Chs57->RenderOverlayRegionViewOnDC(tmp_dc, vp,
2653 get_screen_region);
2654 } else {
2655 ChartPlugInWrapper *ChPI =
2656 dynamic_cast<ChartPlugInWrapper *>(chart);
2657 if (ChPI) {
2658 ChPI->RenderRegionViewOnDC(tmp_dc, vp, get_screen_region);
2659 }
2660 }
2661
2662 OCPNRegionIterator upd(get_screen_region);
2663 while (upd.HaveRects()) {
2664 wxRect rect = upd.GetRect();
2665 dc.Blit(rect.x, rect.y, rect.width, rect.height, &tmp_dc,
2666 rect.x, rect.y, wxCOPY, true);
2667 upd.NextRect();
2668 }
2669 tmp_dc.SelectObject(wxNullBitmap);
2670 }
2671 }
2672 }
2673
2674 chart = GetNextChart();
2675 }
2676 }
2677
2678 // Any part of the chart region that was not rendered in the loop needs
2679 // to be cleared
2680 OCPNRegionIterator clrit(screen_region);
2681 while (clrit.HaveRects()) {
2682 wxRect rect = clrit.GetRect();
2683#ifdef __WXOSX__
2684 dc.SetPen(*wxBLACK_PEN);
2685 dc.SetBrush(*wxBLACK_BRUSH);
2686 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height);
2687#else
2688 dc.Blit(rect.x, rect.y, rect.width, rect.height, &dc, rect.x, rect.y,
2689 wxCLEAR);
2690#endif
2691 clrit.NextRect();
2692 }
2693
2694 // Highlighting....
2695 if (m_nHiLiteIndex >= 0) {
2696 OCPNRegion hiregion =
2697 vp.GetVPRegionIntersect(chart_region, GetHiliteRegion(), 1);
2698 wxRect box = hiregion.GetBox();
2699
2700 if (!box.IsEmpty()) {
2701 // Is scratch member bitmap OK?
2702 if (m_pBM) {
2703 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2704 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2705 delete m_pBM;
2706 m_pBM = NULL;
2707 }
2708 }
2709
2710 if (NULL == m_pBM)
2711 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2712
2713 // Copy the entire quilt to my scratch bm
2714 wxMemoryDC q_dc;
2715 q_dc.SelectObject(*m_pBM);
2716 q_dc.Blit(0, 0, vp.rv_rect.width, vp.rv_rect.height, &dc, 0, 0);
2717 q_dc.SelectObject(wxNullBitmap);
2718
2719 // Create a "mask" bitmap from the chart's region
2720 // WxGTK has an error in this method....Creates a color bitmap, not
2721 // usable for mask creation So, I clone with correction
2722 wxBitmap hl_mask_bm(vp.rv_rect.width, vp.rv_rect.height, 1);
2723 wxMemoryDC mdc;
2724 mdc.SelectObject(hl_mask_bm);
2725 mdc.SetBackground(*wxBLACK_BRUSH);
2726 mdc.Clear();
2727 mdc.SetClippingRegion(box);
2728 mdc.SetBackground(*wxWHITE_BRUSH);
2729 mdc.Clear();
2730 mdc.SelectObject(wxNullBitmap);
2731
2732 if (hl_mask_bm.IsOk()) {
2733 wxMask *phl_mask = new wxMask(hl_mask_bm);
2734 m_pBM->SetMask(phl_mask);
2735 q_dc.SelectObject(*m_pBM);
2736
2737 // Create another mask, dc and bitmap for red-out
2738 wxBitmap rbm(vp.rv_rect.width, vp.rv_rect.height);
2739 wxMask *pr_mask = new wxMask(hl_mask_bm);
2740 wxMemoryDC rdc;
2741 rbm.SetMask(pr_mask);
2742 rdc.SelectObject(rbm);
2743 unsigned char hlcolor = 255;
2744 switch (global_color_scheme) {
2745 case GLOBAL_COLOR_SCHEME_DAY:
2746 hlcolor = 255;
2747 break;
2748 case GLOBAL_COLOR_SCHEME_DUSK:
2749 hlcolor = 64;
2750 break;
2751 case GLOBAL_COLOR_SCHEME_NIGHT:
2752 hlcolor = 16;
2753 break;
2754 default:
2755 hlcolor = 255;
2756 break;
2757 }
2758
2759 rdc.SetBackground(wxBrush(wxColour(hlcolor, 0, 0)));
2760 rdc.Clear();
2761
2762 OCPNRegionIterator upd(hiregion);
2763 while (upd.HaveRects()) {
2764 wxRect rect = upd.GetRect();
2765 rdc.Blit(rect.x, rect.y, rect.width, rect.height, &q_dc, rect.x,
2766 rect.y, wxOR, true);
2767 upd.NextRect();
2768 }
2769
2770 OCPNRegionIterator updq(hiregion);
2771 while (updq.HaveRects()) {
2772 wxRect rect = updq.GetRect();
2773 q_dc.Blit(rect.x, rect.y, rect.width, rect.height, &rdc, rect.x,
2774 rect.y, wxCOPY, true);
2775 updq.NextRect();
2776 }
2777
2778 q_dc.SelectObject(wxNullBitmap);
2779 m_pBM->SetMask(NULL);
2780
2781 // Select the scratch BM as the return dc contents
2782 dc.SelectObject(*m_pBM);
2783
2784 // Clear the rdc
2785 rdc.SelectObject(wxNullBitmap);
2786 }
2787 } // box not empty
2788 } // m_nHiLiteIndex
2789
2790 // Fogging....
2791 if (g_fog_overzoom) {
2792 double scale_factor = vp.ref_scale / vp.chart_scale;
2793
2794 if (scale_factor > g_overzoom_emphasis_base) {
2795 float fog = ((scale_factor - g_overzoom_emphasis_base) * 255.) / 20.;
2796 fog = wxMin(fog, 200.); // Don't fog out completely
2797
2798 // Is scratch member bitmap OK?
2799 if (m_pBM) {
2800 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2801 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2802 delete m_pBM;
2803 m_pBM = NULL;
2804 }
2805 }
2806
2807 if (NULL == m_pBM)
2808 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2809
2810 // Copy the entire quilt to my scratch bm
2811 wxMemoryDC q_dc;
2812 q_dc.SelectObject(*m_pBM);
2813 q_dc.Blit(0, 0, vp.rv_rect.width, vp.rv_rect.height, &dc, 0, 0);
2814 q_dc.SelectObject(wxNullBitmap);
2815
2816 wxImage src = m_pBM->ConvertToImage();
2817#if 1
2818 int blur_factor =
2819 wxMin((scale_factor - g_overzoom_emphasis_base) / 4, 4);
2820 if (src.IsOk()) {
2821 wxImage dest = src.Blur(blur_factor);
2822#endif
2823
2824#if 0 // this is fogging effect
2825 unsigned char *bg = src.GetData();
2826 wxColour color = m_parent->GetFogColor();
2827
2828 float transparency = fog;
2829
2830 // destination image
2831 wxImage dest(vp.rv_rect.width, vp.rv_rect.height);
2832 unsigned char *dest_data = (unsigned char *) malloc( vp.rv_rect.width * vp.rv_rect.height * 3 * sizeof(unsigned char) );
2833 unsigned char *d = dest_data;
2834
2835 float alpha = 1.0 - (float)transparency / 255.0;
2836 int sb = vp.rv_rect.width * vp.rv_rect.height;
2837 for( int i = 0; i < sb; i++ ) {
2838 float a = alpha;
2839
2840 int r = ( ( *bg++ ) * a ) + (1.0-a) * color.Red();
2841 *d++ = r;
2842 int g = ( ( *bg++ ) * a ) + (1.0-a) * color.Green();
2843 *d++ = g;
2844 int b = ( ( *bg++ ) * a ) + (1.0-a) * color.Blue();
2845 *d++ = b;
2846 }
2847
2848 dest.SetData( dest_data );
2849#endif
2850
2851 wxBitmap dim(dest);
2852 wxMemoryDC ddc;
2853 ddc.SelectObject(dim);
2854
2855 q_dc.SelectObject(*m_pBM);
2856 OCPNRegionIterator upd(rendered_region);
2857 while (upd.HaveRects()) {
2858 wxRect rect = upd.GetRect();
2859 q_dc.Blit(rect.x, rect.y, rect.width, rect.height, &ddc, rect.x,
2860 rect.y);
2861 upd.NextRect();
2862 }
2863
2864 ddc.SelectObject(wxNullBitmap);
2865 q_dc.SelectObject(wxNullBitmap);
2866
2867 // Select the scratch BM as the return dc contents
2868 dc.SelectObject(*m_pBM);
2869 }
2870 }
2871 } // overzoom
2872
2873 if (!dc.IsOk()) // some error, probably bad charts, to be disabled on next
2874 // compose
2875 {
2876 SubstituteClearDC(dc, vp);
2877 }
2878
2879 } else { // no charts yet, or busy....
2880 SubstituteClearDC(dc, vp);
2881 }
2882
2883 // Record the region actually rendered
2884 m_rendered_region = rendered_region;
2885
2886 m_vp_rendered = vp;
2887 return true;
2888}
2889
2890void Quilt::SubstituteClearDC(wxMemoryDC &dc, ViewPort &vp) {
2891 if (m_pBM) {
2892 if ((m_pBM->GetWidth() != vp.rv_rect.width) ||
2893 (m_pBM->GetHeight() != vp.rv_rect.height)) {
2894 delete m_pBM;
2895 m_pBM = NULL;
2896 }
2897 }
2898
2899 if (NULL == m_pBM) {
2900 m_pBM = new wxBitmap(vp.rv_rect.width, vp.rv_rect.height);
2901 }
2902
2903 dc.SelectObject(wxNullBitmap);
2904 dc.SelectObject(*m_pBM);
2905 dc.SetBackground(*wxBLACK_BRUSH);
2906 dc.Clear();
2907 m_covered_region.Clear();
2908}
2909
2910bool Quilt::DoRenderQuiltRegionViewOnDCTextOnly(wxMemoryDC &dc, ViewPort &vp,
2911 OCPNRegion &chart_region) {
2912 if (!m_bcomposed) return false;
2913
2914 OCPNRegion rendered_region;
2915
2916 if (GetnCharts() && !m_bbusy) {
2917 OCPNRegion screen_region = chart_region;
2918
2919 // Walk the quilt, drawing each chart from largest scale to smallest
2920
2921 ChartBase *chart = GetLargestScaleChart();
2922
2923 while (chart) {
2924 QuiltPatch *pqp = GetCurrentPatch();
2925 if (pqp->b_Valid) {
2926 s57chart *Chs57 = dynamic_cast<s57chart *>(chart);
2927 if (Chs57)
2928 Chs57->RenderRegionViewOnDCTextOnly(dc, vp, chart_region);
2929 else {
2930 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
2931 if (ChPI) {
2932 ChPI->RenderRegionViewOnDCTextOnly(dc, vp, chart_region);
2933 }
2934 }
2935 }
2936
2937 chart = GetNextSmallerScaleChart();
2938 }
2939
2940 } else { // no charts yet, or busy....
2941 SubstituteClearDC(dc, vp);
2942 }
2943
2944 return true;
2945}
bool Compose(const ViewPort &vp)
Definition: Quilt.cpp:1643
Definition: Quilt.cpp:864