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