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