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