OpenCPN Partial API docs
Loading...
Searching...
No Matches
viewport.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: ViewPort
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2015 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26// For compilers that support precompilation, includes "wx.h".
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif // precompiled headers
32#include <wx/image.h>
33#include <wx/graphics.h>
34#include <wx/listbook.h>
35#include <wx/clipbrd.h>
36#include <wx/aui/aui.h>
37
38#if defined(__OCPN__ANDROID__)
39#include <GLES2/gl2.h>
40#elif defined(__WXQT__) || defined(__WXGTK__)
41#include <GL/glew.h>
42#endif
43
44#include "config.h"
45
46#include "dychart.h"
47
48#include <wx/listimpl.cpp>
49
50#include "chcanv.h"
51#include "TCWin.h"
52#include "model/geodesic.h"
53#include "styles.h"
54#include "model/routeman.h"
55#include "navutil.h"
56#include "kml.h"
57#include "concanv.h"
58#include "thumbwin.h"
59#include "chartdb.h"
60#include "chartimg.h"
61#include "model/cutil.h"
62#include "TrackPropDlg.h"
63#include "tcmgr.h"
64#include "routemanagerdialog.h"
65#include "pluginmanager.h"
66#include "ocpn_pixel.h"
67#include "ocpndc.h"
68#include "undo.h"
69#include "model/multiplexer.h"
70#include "timers.h"
71#include "tide_time.h"
72#include "glTextureDescriptor.h"
73#include "ch_info_win.h"
74#include "Quilt.h"
75#include "model/select_item.h"
76#include "model/select.h"
77#include "FontMgr.h"
78#include "model/ais_decoder.h"
79#include "model/ais_target_data.h"
81#include "SendToGpsDlg.h"
82#include "OCPNRegion.h"
83#include "gshhs.h"
84
85#ifdef ocpnUSE_GL
86#include "glChartCanvas.h"
87#endif
88
89#include "cm93.h" // for chart outline draw
90#include "s57chart.h" // for ArrayOfS57Obj
91#include "s52plib.h"
92
93#include "ais.h"
94
95#ifdef __VISUALC__
96#include <wx/msw/msvcrt.h>
97#endif
98
99#ifndef __WXMSW__
100#include <signal.h>
101#include <setjmp.h>
102
103extern struct sigaction sa_all_old;
104
105extern sigjmp_buf env; // the context saved by sigsetjmp();
106#endif
107
108#include <vector>
109
110ColorScheme global_color_scheme = GLOBAL_COLOR_SCHEME_DAY;
111
112// ----------------------------------------------------------------------------
113// Useful Prototypes
114// ----------------------------------------------------------------------------
115
116extern void catch_signals(int signo);
117
118//------------------------------------------------------------------------------
119// ViewPort Implementation
120//------------------------------------------------------------------------------
121ViewPort::ViewPort() {
122 bValid = false;
123 skew = 0.;
124 view_scale_ppm = 1;
125 rotation = 0.;
126 tilt = 0.;
127 b_quilt = false;
128 pix_height = pix_width = 0;
129 b_MercatorProjectionOverride = false;
130 lat0_cache = NAN;
131 m_projection_type = PROJECTION_MERCATOR;
132 m_displayScale = 1.0;
133}
134
135void ViewPort::SetPixelScale(double scale) { m_displayScale = scale; }
136
137// TODO: eliminate the use of this function
138wxPoint ViewPort::GetPixFromLL(double lat, double lon) {
139 wxPoint2DDouble p = GetDoublePixFromLL(lat, lon);
140 if (wxFinite(p.m_x) && wxFinite(p.m_y)) {
141 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
142 return wxPoint(wxRound(p.m_x), wxRound(p.m_y));
143 }
144 return wxPoint(INVALID_COORD, INVALID_COORD);
145}
146
147wxPoint2DDouble ViewPort::GetDoublePixFromLL(double lat, double lon) {
148 double easting = 0;
149 double northing = 0;
150 double xlon = lon;
151
152 /* Make sure lon and lon0 are same phase */
153 if (xlon * clon < 0.) {
154 if (xlon < 0.)
155 xlon += 360.;
156 else
157 xlon -= 360.;
158 }
159
160 if (fabs(xlon - clon) > 180.) {
161 if (xlon > clon)
162 xlon -= 360.;
163 else
164 xlon += 360.;
165 }
166
167 // update cache of trig functions used for projections
168 if (clat != lat0_cache) {
169 lat0_cache = clat;
170 switch (m_projection_type) {
171 case PROJECTION_MERCATOR:
172 case PROJECTION_WEB_MERCATOR:
173 cache0 = toSMcache_y30(clat);
174 break;
175 case PROJECTION_POLAR:
176 cache0 = toPOLARcache_e(clat);
177 break;
178 case PROJECTION_ORTHOGRAPHIC:
179 case PROJECTION_STEREOGRAPHIC:
180 case PROJECTION_GNOMONIC:
181 cache_phi0(clat, &cache0, &cache1);
182 break;
183 }
184 }
185
186 switch (m_projection_type) {
187 case PROJECTION_MERCATOR:
188 case PROJECTION_WEB_MERCATOR:
189 toSMcache(lat, xlon, cache0, clon, &easting, &northing);
190 break;
191
192 case PROJECTION_TRANSVERSE_MERCATOR:
193 // We calculate northings as referenced to the equator
194 // And eastings as though the projection point is midscreen.
195
196 double tmeasting, tmnorthing;
197 double tmceasting, tmcnorthing;
198 toTM(clat, clon, 0., clon, &tmceasting, &tmcnorthing);
199 toTM(lat, xlon, 0., clon, &tmeasting, &tmnorthing);
200
201 northing = tmnorthing - tmcnorthing;
202 easting = tmeasting - tmceasting;
203 break;
204
205 case PROJECTION_POLYCONIC:
206
207 // We calculate northings as referenced to the equator
208 // And eastings as though the projection point is midscreen.
209 double pceasting, pcnorthing;
210 toPOLY(clat, clon, 0., clon, &pceasting, &pcnorthing);
211
212 double peasting, pnorthing;
213 toPOLY(lat, xlon, 0., clon, &peasting, &pnorthing);
214
215 easting = peasting;
216 northing = pnorthing - pcnorthing;
217 break;
218
219 case PROJECTION_ORTHOGRAPHIC:
220 toORTHO(lat, xlon, cache0, cache1, clon, &easting, &northing);
221 break;
222
223 case PROJECTION_POLAR:
224 toPOLAR(lat, xlon, cache0, clat, clon, &easting, &northing);
225 break;
226
227 case PROJECTION_STEREOGRAPHIC:
228 toSTEREO(lat, xlon, cache0, cache1, clon, &easting, &northing);
229 break;
230
231 case PROJECTION_GNOMONIC:
232 toGNO(lat, xlon, cache0, cache1, clon, &easting, &northing);
233 break;
234
235 case PROJECTION_EQUIRECTANGULAR:
236 toEQUIRECT(lat, xlon, clat, clon, &easting, &northing);
237 break;
238
239 default:
240 printf("unhandled projection\n");
241 }
242
243 if (!wxFinite(easting) || !wxFinite(northing))
244 return wxPoint2DDouble(easting, northing);
245
246 double epix = easting * view_scale_ppm;
247 double npix = northing * view_scale_ppm;
248 double dxr = epix;
249 double dyr = npix;
250
251 // Apply VP Rotation
252 double angle = rotation;
253
254 if (angle) {
255 dxr = epix * cos(angle) + npix * sin(angle);
256 dyr = npix * cos(angle) - epix * sin(angle);
257 }
258 double x = (pix_width / 2.0) + dxr;
259 double y = (pix_height / 2.0) - dyr;
260 if (!g_bopengl) {
261 // Convert from physical to logical pixels when not using OpenGL.
262 // This ensures that the viewport corner coordinates remain the
263 // same in OpenGL and no-OpenGL mode (especially in MacOS).
264 x /= m_displayScale;
265 y /= m_displayScale;
266 }
267 return wxPoint2DDouble(x, y);
268}
269
270void ViewPort::GetLLFromPix(const wxPoint2DDouble &p, double *lat,
271 double *lon) {
272 // Calculate distance from the center of the viewport to the given point in
273 // physical pixels.
274 double dx = p.m_x - (pix_width / 2.0);
275 double dy = (pix_height / 2.0) - p.m_y;
276
277 double xpr = dx;
278 double ypr = dy;
279
280 // Apply VP Rotation
281 double angle = rotation;
282
283 if (angle) {
284 xpr = (dx * cos(angle)) - (dy * sin(angle));
285 ypr = (dy * cos(angle)) + (dx * sin(angle));
286 }
287 double d_east = xpr / view_scale_ppm;
288 double d_north = ypr / view_scale_ppm;
289
290 double slat = 0.0, slon = 0.0;
291 switch (m_projection_type) {
292 case PROJECTION_MERCATOR:
293 case PROJECTION_WEB_MERCATOR:
294 // TODO This could be fromSM_ECC to better match some Raster charts
295 // However, it seems that cm93 (and S57) prefer no eccentricity
296 // correction Think about it....
297 fromSM(d_east, d_north, clat, clon, &slat, &slon);
298 break;
299
300 case PROJECTION_TRANSVERSE_MERCATOR: {
301 double tmceasting, tmcnorthing;
302 toTM(clat, clon, 0., clon, &tmceasting, &tmcnorthing);
303
304 fromTM(d_east, d_north + tmcnorthing, 0., clon, &slat, &slon);
305 } break;
306
307 case PROJECTION_POLYCONIC: {
308 double polyeasting, polynorthing;
309 toPOLY(clat, clon, 0., clon, &polyeasting, &polynorthing);
310
311 fromPOLY(d_east, d_north + polynorthing, 0., clon, &slat, &slon);
312 } break;
313
314 case PROJECTION_ORTHOGRAPHIC:
315 fromORTHO(d_east, d_north, clat, clon, &slat, &slon);
316 break;
317
318 case PROJECTION_POLAR:
319 fromPOLAR(d_east, d_north, clat, clon, &slat, &slon);
320 break;
321
322 case PROJECTION_STEREOGRAPHIC:
323 fromSTEREO(d_east, d_north, clat, clon, &slat, &slon);
324 break;
325
326 case PROJECTION_GNOMONIC:
327 fromGNO(d_east, d_north, clat, clon, &slat, &slon);
328 break;
329
330 case PROJECTION_EQUIRECTANGULAR:
331 fromEQUIRECT(d_east, d_north, clat, clon, &slat, &slon);
332 break;
333
334 default:
335 printf("unhandled projection\n");
336 }
337
338 *lat = slat;
339
340 if (slon < -180.)
341 slon += 360.;
342 else if (slon > 180.)
343 slon -= 360.;
344 *lon = slon;
345}
346
347LLRegion ViewPort::GetLLRegion(const OCPNRegion &region) {
348 // todo: for these projecetions, improve this calculation by using the
349 // method in SetBoxes here
350#ifndef ocpnUSE_GL
351 return LLRegion(GetBBox());
352#else
353
354 if (!glChartCanvas::CanClipViewport(*this)) return LLRegion(GetBBox());
355
356 OCPNRegionIterator it(region);
357 LLRegion r;
358 while (it.HaveRects()) {
359 wxRect rect = it.GetRect();
360
361 int x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height;
362 int p[8] = {x1, y1, x2, y1, x2, y2, x1, y2};
363 double pll[2896]; // Max splits is 180, ((180 * 2) + 2) * 8 = 2896.
364 int j;
365
366 /* if the viewport is rotated, we must split the segments as straight lines
367 in lat/lon coordinates map to curves in projected coordinate space */
368 if (fabs(rotation) >= 0.0001) {
369 j = 0;
370 double lastlat, lastlon;
371 int li = 6;
372 GetLLFromPix(wxPoint(p[li], p[li + 1]), &lastlat, &lastlon);
373 for (int i = 0; i < 8; i += 2) {
374 double lat, lon;
375 GetLLFromPix(wxPoint(p[i], p[i + 1]), &lat, &lon);
376
377 // use 2 degree grid
378 double grid = 2;
379 int lat_splits = floor(fabs(lat - lastlat) / grid);
380 double lond = fabs(lon - lastlon);
381 int lon_splits = floor((lond > 180 ? 360 - lond : lond) / grid);
382 int splits = wxMax(lat_splits, lon_splits) + 1;
383
384 for (int k = 1; k < splits; k++) {
385 float d = (float)k / splits;
386 GetLLFromPix(wxPoint((1 - d) * p[li] + d * p[i],
387 (1 - d) * p[li + 1] + d * p[i + 1]),
388 pll + j, pll + j + 1);
389 j += 2;
390 }
391 pll[j++] = lat;
392 pll[j++] = lon;
393 li = i;
394 lastlat = lat, lastlon = lon;
395 }
396 } else {
397 j = 8;
398 for (int i = 0; i < j; i += 2)
399 GetLLFromPix(wxPoint(p[i], p[i + 1]), pll + i, pll + i + 1);
400 }
401
402 // resolve (this works even if rectangle crosses both 0 and 180)
403 for (int i = 0; i < j; i += 2) {
404 if (pll[i + 1] <= clon - 180)
405 pll[i + 1] += 360;
406 else if (pll[i + 1] >= clon + 180)
407 pll[i + 1] -= 360;
408 }
409
410 r.Union(LLRegion(j / 2, pll));
411 it.NextRect();
412 }
413 return r;
414#endif
415}
416
418 double maxlat;
419 bool subtract;
420 OCPNRegion r;
421};
422
424 const LLRegion &llregion,
425 int chart_native_scale) {
426 double rotation_save = rotation;
427 rotation = 0;
428
429 std::list<ContourRegion> cregions;
430 for (std::list<poly_contour>::const_iterator i = llregion.contours.begin();
431 i != llregion.contours.end(); i++) {
432 float *contour_points = new float[2 * i->size()];
433 int idx = 0;
434 std::list<contour_pt>::const_iterator j;
435 for (j = i->begin(); j != i->end(); j++) {
436 contour_points[idx++] = j->y;
437 contour_points[idx++] = j->x;
438 }
439
440 double total = 0, maxlat = -90;
441 int pl = idx - 2;
442 double x0 = contour_points[0] - contour_points[pl + 0];
443 double y0 = contour_points[1] - contour_points[pl + 1];
444 // determine winding direction of this contour
445 for (int p = 0; p < idx; p += 2) {
446 maxlat = wxMax(maxlat, contour_points[p]);
447 int pn = p < idx - 2 ? p + 2 : 0;
448 double x1 = contour_points[pn + 0] - contour_points[p + 0];
449 double y1 = contour_points[pn + 1] - contour_points[p + 1];
450 total += x1 * y0 - x0 * y1;
451 x0 = x1, y0 = y1;
452 }
453
455 s.maxlat = maxlat;
456 s.subtract = total < 0;
457 s.r = GetVPRegionIntersect(region, i->size(), contour_points,
458 chart_native_scale, NULL);
459 delete[] contour_points;
460
461 std::list<ContourRegion>::iterator k = cregions.begin();
462 while (k != cregions.end()) {
463 if (k->maxlat < s.maxlat) break;
464 k++;
465 }
466 cregions.insert(k, s);
467 }
468
469 OCPNRegion r;
470 for (std::list<ContourRegion>::iterator k = cregions.begin();
471 k != cregions.end(); k++) {
472 if (k->r.Ok()) {
473 if (k->subtract)
474 r.Subtract(k->r);
475 else
476 r.Union(k->r);
477 }
478 }
479
480 rotation = rotation_save;
481 return r;
482}
483
485 float *llpoints,
486 int chart_native_scale,
487 wxPoint *ppoints) {
488 // Calculate the intersection between a given OCPNRegion (Region) and a
489 // polygon specified by lat/lon points.
490
491 // If the viewpoint is highly overzoomed wrt to chart native scale, the
492 // polygon region may be huge. This can be very expensive, and lead to
493 // crashes on some platforms (gtk in particular) So, look for this case and
494 // handle appropriately with respect to the given Region
495
496 if (chart_scale < chart_native_scale / 10) {
497 // Scan the points one-by-one, so that we can get min/max to make a bbox
498 float *pfp = llpoints;
499 float lon_max = -10000.;
500 float lon_min = 10000.;
501 float lat_max = -10000.;
502 float lat_min = 10000.;
503
504 for (int ip = 0; ip < nPoints; ip++) {
505 lon_max = wxMax(lon_max, pfp[1]);
506 lon_min = wxMin(lon_min, pfp[1]);
507 lat_max = wxMax(lat_max, pfp[0]);
508 lat_min = wxMin(lat_min, pfp[0]);
509
510 pfp += 2;
511 }
512
513 LLBBox chart_box;
514 chart_box.Set(lat_min, lon_min, lat_max, lon_max);
515
516 // Case: vpBBox is completely outside the chart box, or vice versa
517 // Return an empty region
518 if (chart_box.IntersectOut(vpBBox)) return OCPNRegion();
519
520 // Case: vpBBox is completely inside the chart box
521 // Note that this test is not perfect, and will fail for some charts.
522 // The chart coverage may be essentially triangular, and the viewport
523 // box may be in the "cut off" segment of the chart_box, and not
524 // actually exhibit any true overlap. Results will be reported
525 // incorrectly. How to fix: maybe scrub the chart points and see if it
526 // is likely that a region may be safely built and intersection tested.
527
528 if (chart_box.IntersectIn(vpBBox)) return Region;
529
530 wxPoint p1 = GetPixFromLL(lat_max, lon_min); // upper left
531 wxPoint p2 = GetPixFromLL(lat_min, lon_max); // lower right
532
533 OCPNRegion r(p1, p2);
534 r.Intersect(Region);
535 return r;
536 }
537
538 // More "normal" case
539
540 wxPoint *pp;
541
542 // Use the passed point buffer if available
543 if (ppoints == NULL)
544 pp = new wxPoint[nPoints];
545 else
546 pp = ppoints;
547
548 float *pfp = llpoints;
549
550 wxPoint p = GetPixFromLL(pfp[0], pfp[1]);
551 int poly_x_max = INVALID_COORD, poly_y_max = INVALID_COORD,
552 poly_x_min = INVALID_COORD, poly_y_min = INVALID_COORD;
553
554 bool valid = false;
555 int npPoints = 0;
556 for (int ip = 0; ip < nPoints; ip++) {
557 wxPoint p = GetPixFromLL(pfp[0], pfp[1]);
558 if (p.x == INVALID_COORD) continue;
559
560 pp[npPoints++] = p;
561
562 if (valid) {
563 poly_x_max = wxMax(poly_x_max, p.x);
564 poly_y_max = wxMax(poly_y_max, p.y);
565 poly_x_min = wxMin(poly_x_min, p.x);
566 poly_y_min = wxMin(poly_y_min, p.y);
567 } else {
568 poly_x_max = p.x;
569 poly_y_max = p.y;
570 poly_x_min = p.x;
571 poly_y_min = p.y;
572 valid = true;
573 }
574 pfp += 2;
575 }
576
577 if (!valid) {
578 if (ppoints == NULL) delete[] pp;
579 return OCPNRegion(); // empty;
580 }
581
582 // We want to avoid processing regions with very large rectangle counts,
583 // so make some tests for special cases
584
585 float_2Dpt p0, p1, p2, p3;
586
587 // First, calculate whether any segment of the input polygon intersects the
588 // specified Region
589 int nrect = 0;
590 bool b_intersect = false;
591 OCPNRegionIterator screen_region_it1(Region);
592 while (screen_region_it1.HaveRects()) {
593 wxRect rect = screen_region_it1.GetRect();
594
595 double lat, lon;
596
597 // The screen region corners
598 GetLLFromPix(wxPoint(rect.x, rect.y), &lat, &lon);
599 p0.y = lat;
600 p0.x = lon;
601
602 GetLLFromPix(wxPoint(rect.x + rect.width, rect.y), &lat, &lon);
603 p1.y = lat;
604 p1.x = lon;
605
606 GetLLFromPix(wxPoint(rect.x + rect.width, rect.y + rect.height), &lat,
607 &lon);
608 p2.y = lat;
609 p2.x = lon;
610
611 GetLLFromPix(wxPoint(rect.x, rect.y + rect.height), &lat, &lon);
612 p3.y = lat;
613 p3.x = lon;
614
615 for (int i = 0; i < npPoints - 1; i++) {
616 // Quick check on y dimension
617 int y0 = pp[i].y;
618 int y1 = pp[i + 1].y;
619
620 if (((y0 < rect.y) && (y1 < rect.y)) ||
621 ((y0 > rect.y + rect.height) && (y1 > rect.y + rect.height)))
622 continue; // both ends of line outside of box, top or bottom
623
624 // Look harder
625 float_2Dpt f0;
626 f0.y = llpoints[i * 2];
627 f0.x = llpoints[(i * 2) + 1];
628 float_2Dpt f1;
629 f1.y = llpoints[(i + 1) * 2];
630 f1.x = llpoints[((i + 1) * 2) + 1];
631 b_intersect |= Intersect_FL(p0, p1, f0, f1) != 0;
632 if (b_intersect) break;
633 b_intersect |= Intersect_FL(p1, p2, f0, f1) != 0;
634 if (b_intersect) break;
635 b_intersect |= Intersect_FL(p2, p3, f0, f1) != 0;
636 if (b_intersect) break;
637 b_intersect |= Intersect_FL(p3, p0, f0, f1) != 0;
638 if (b_intersect) break;
639
640 // Must check the case where the input polygon has been pre-normalized,
641 // eg (0 < lon < 360), as cm93
642 f0.x -= 360.;
643 f1.x -= 360.;
644 b_intersect |= Intersect_FL(p0, p1, f0, f1) != 0;
645 if (b_intersect) break;
646 b_intersect |= Intersect_FL(p1, p2, f0, f1) != 0;
647 if (b_intersect) break;
648 b_intersect |= Intersect_FL(p2, p3, f0, f1) != 0;
649 if (b_intersect) break;
650 b_intersect |= Intersect_FL(p3, p0, f0, f1) != 0;
651 if (b_intersect) break;
652 }
653
654 // Check segment, last point back to first point
655 if (!b_intersect) {
656 float_2Dpt f0;
657 f0.y = llpoints[(nPoints - 1) * 2];
658 f0.x = llpoints[((nPoints - 1) * 2) + 1];
659 float_2Dpt f1;
660 f1.y = llpoints[0];
661 f1.x = llpoints[1];
662 b_intersect |= Intersect_FL(p0, p1, f0, f1) != 0;
663 b_intersect |= Intersect_FL(p1, p2, f0, f1) != 0;
664 b_intersect |= Intersect_FL(p2, p3, f0, f1) != 0;
665 b_intersect |= Intersect_FL(p3, p0, f0, f1) != 0;
666
667 f0.x -= 360.;
668 f1.x -= 360.;
669 b_intersect |= Intersect_FL(p0, p1, f0, f1) != 0;
670 b_intersect |= Intersect_FL(p1, p2, f0, f1) != 0;
671 b_intersect |= Intersect_FL(p2, p3, f0, f1) != 0;
672 b_intersect |= Intersect_FL(p3, p0, f0, f1) != 0;
673 }
674
675 screen_region_it1.NextRect();
676 nrect++;
677 }
678
679 // If there is no itersection, we need to consider the case where
680 // the subject polygon is entirely within the Region
681 bool b_contained = false;
682 if (!b_intersect) {
683 OCPNRegionIterator screen_region_it2(Region);
684 while (screen_region_it2.HaveRects()) {
685 wxRect rect = screen_region_it2.GetRect();
686
687 for (int i = 0; i < npPoints - 1; i++) {
688 int x0 = pp[i].x;
689 int y0 = pp[i].y;
690
691 if ((x0 < rect.x) || (x0 > rect.x + rect.width) || (y0 < rect.y) ||
692 (y0 > rect.y + rect.height))
693 continue;
694
695 b_contained = true;
696 break;
697 }
698 screen_region_it2.NextRect();
699 }
700 }
701
702#if 1
703 // and here is the payoff
704 if (!b_contained && !b_intersect) {
705 // Two cases to consider
706 wxRect rpoly(poly_x_min, poly_y_min, poly_x_max - poly_x_min,
707 poly_y_max - poly_y_min);
708 wxRect rRegion = Region.GetBox();
709 if (rpoly.Contains(rRegion)) {
710 // subject poygon may be large enough to fully encompass the target
711 // Region, but it might not, especially for irregular or concave charts.
712 // So we cannot directly shortcut here
713 // Better check....
714
715#if 1
716 if (nrect == 1) { // most common case
717 // If the subject polygon contains the center of the target rectangle,
718 // then the intersection must be the target rectangle
719 float rlat = (p0.y + p2.y) / 2.;
720 float rlon = (p0.x + p1.x) / 2.;
721
722 if (G_PtInPolygon_FL((float_2Dpt *)llpoints, nPoints, rlon, rlat)) {
723 if (NULL == ppoints) delete[] pp;
724 return Region;
725 }
726 rlon += 360.;
727 if (G_PtInPolygon_FL((float_2Dpt *)llpoints, nPoints, rlon, rlat)) {
728 if (NULL == ppoints) delete[] pp;
729 return Region;
730 }
731
732 // otherwise, there is no intersection
733 else {
734 if (NULL == ppoints) delete[] pp;
735 wxRegion r;
736 return r;
737 }
738 }
739
740#endif
741
742 } else {
743 // Subject polygon is entirely outside of target Region
744 // so the intersection must be empty.
745 if (NULL == ppoints) delete[] pp;
746 wxRegion r;
747 return r;
748 }
749 } else if (b_contained && !b_intersect) {
750 // subject polygon is entirely withing the target Region,
751 // so the intersection is the subject polygon
752 OCPNRegion r = OCPNRegion(npPoints, pp);
753 if (NULL == ppoints) delete[] pp;
754 return r;
755 }
756
757#endif
758
759#ifdef __WXGTK__
760 sigaction(SIGSEGV, NULL,
761 &sa_all_old); // save existing action for this signal
762
763 struct sigaction temp;
764 sigaction(SIGSEGV, NULL, &temp); // inspect existing action for this signal
765
766 temp.sa_handler = catch_signals; // point to my handler
767 sigemptyset(&temp.sa_mask); // make the blocking set
768 // empty, so that all
769 // other signals will be
770 // unblocked during my handler
771 temp.sa_flags = 0;
772 sigaction(SIGSEGV, &temp, NULL);
773
774 if (sigsetjmp(env, 1)) // Something in the below code block faulted....
775 {
776 sigaction(SIGSEGV, &sa_all_old, NULL); // reset signal handler
777
778 return Region;
779
780 }
781
782 else {
783 OCPNRegion r = OCPNRegion(npPoints, pp);
784 if (NULL == ppoints) delete[] pp;
785
786 sigaction(SIGSEGV, &sa_all_old, NULL); // reset signal handler
787 r.Intersect(Region);
788 return r;
789 }
790
791#else
792 OCPNRegion r = OCPNRegion(npPoints, pp);
793
794 if (NULL == ppoints) delete[] pp;
795
796 r.Intersect(Region);
797 return r;
798
799#endif
800}
801
802wxRect ViewPort::GetVPRectIntersect(size_t n, float *llpoints) {
803 // Calculate the intersection between the currect VP screen
804 // and the bounding box of a polygon specified by lat/lon points.
805
806 float *pfp = llpoints;
807
808 BoundingBox point_box;
809 for (unsigned int ip = 0; ip < n; ip++) {
810 point_box.Expand(pfp[1], pfp[0]);
811 pfp += 2;
812 }
813
814 wxPoint pul = GetPixFromLL(point_box.GetMaxY(), point_box.GetMinX());
815 wxPoint plr = GetPixFromLL(point_box.GetMinY(), point_box.GetMaxX());
816
817 OCPNRegion r(pul, plr);
818 OCPNRegion rs(rv_rect);
819
820 r.Intersect(rs);
821
822 return r.GetBox();
823}
824
826 // In the case where canvas rotation is applied, we need to define a larger
827 // "virtual" pixel window size to ensure that enough chart data is fatched
828 // and available to fill the rotated screen.
829 rv_rect = wxRect(0, 0, pix_width, pix_height);
830
831 // Specify the minimum required rectangle in unrotated screen space which
832 // will supply full screen data after specified rotation
833 if ((fabs(skew) > .0001) || (fabs(rotation) > .0001)) {
834 double rotator = rotation;
835 double lpixh = pix_height;
836 double lpixw = pix_width;
837
838 lpixh = wxMax(lpixh,
839 (fabs(pix_height * cos(skew)) + fabs(pix_width * sin(skew))));
840 lpixw = wxMax(lpixw,
841 (fabs(pix_width * cos(skew)) + fabs(pix_height * sin(skew))));
842
843 int dy = wxRound(fabs(lpixh * cos(rotator)) + fabs(lpixw * sin(rotator)));
844 int dx = wxRound(fabs(lpixw * cos(rotator)) + fabs(lpixh * sin(rotator)));
845
846 // It is important for MSW build that viewport pixel dimensions be
847 // multiples of 4.....
848 if (dy % 4) dy += 4 - (dy % 4);
849 if (dx % 4) dx += 4 - (dx % 4);
850
851 int inflate_x = wxMax((dx - pix_width) / 2, 0);
852 int inflate_y = wxMax((dy - pix_height) / 2, 0);
853
854 // Grow the source rectangle appropriately
855 rv_rect.Inflate(inflate_x, inflate_y);
856 }
857
858 // Compute Viewport lat/lon reference points for co-ordinate hit testing
859
860 // This must be done in unrotated space with respect to full unrotated screen
861 // space calculated above
862 double rotation_save = rotation;
863 SetRotationAngle(0.0);
864
865 wxPoint ul(rv_rect.x, rv_rect.y); // Upper left.
866 wxPoint lr(rv_rect.x + rv_rect.width,
867 rv_rect.y + rv_rect.height); // Lower right.
868 double dlat_min, dlat_max, dlon_min, dlon_max;
869
870 bool hourglass = false;
871 switch (m_projection_type) {
872 case PROJECTION_TRANSVERSE_MERCATOR:
873 case PROJECTION_STEREOGRAPHIC:
874 case PROJECTION_GNOMONIC:
875 hourglass = true;
876 // fall through
877 case PROJECTION_POLYCONIC:
878 case PROJECTION_POLAR:
879 case PROJECTION_ORTHOGRAPHIC: {
880 double d;
881
882 if (clat > 0) { // north polar
883 wxPoint u(rv_rect.x + rv_rect.width / 2, rv_rect.y);
884 wxPoint ur(rv_rect.x + rv_rect.width, rv_rect.y);
885 GetLLFromPix(ul, &d, &dlon_min);
886 GetLLFromPix(ur, &d, &dlon_max);
887 GetLLFromPix(lr, &dlat_min, &d);
888 GetLLFromPix(u, &dlat_max, &d);
889
890 if (fabs(fabs(d - clon) - 180) < 1) { // the pole is onscreen
891 dlat_max = 90;
892 dlon_min = -180;
893 dlon_max = 180;
894 } else if (std::isnan(dlat_max))
895 dlat_max = 90;
896
897 if (hourglass) {
898 // near equator, center may be less
899 wxPoint l(rv_rect.x + rv_rect.width / 2, rv_rect.y + rv_rect.height);
900 double dlat_min2;
901 GetLLFromPix(l, &dlat_min2, &d);
902 dlat_min = wxMin(dlat_min, dlat_min2);
903 }
904
905 if (std::isnan(dlat_min)) // world is off-screen
906 dlat_min = clat - 90;
907 } else { // south polar
908 wxPoint l(rv_rect.x + rv_rect.width / 2, rv_rect.y + rv_rect.height);
909 wxPoint ll(rv_rect.x, rv_rect.y + rv_rect.height);
910 GetLLFromPix(ul, &dlat_max, &d);
911 GetLLFromPix(lr, &d, &dlon_max);
912 GetLLFromPix(ll, &d, &dlon_min);
913 GetLLFromPix(l, &dlat_min, &d);
914
915 if (fabs(fabs(d - clon) - 180) < 1) { // the pole is onscreen
916 dlat_min = -90;
917 dlon_min = -180;
918 dlon_max = 180;
919 } else if (std::isnan(dlat_min))
920 dlat_min = -90;
921
922 if (hourglass) {
923 // near equator, center may be less
924 wxPoint u(rv_rect.x + rv_rect.width / 2, rv_rect.y);
925 double dlat_max2;
926 GetLLFromPix(u, &dlat_max2, &d);
927 dlat_max = wxMax(dlat_max, dlat_max2);
928 }
929
930 if (std::isnan(dlat_max)) // world is off-screen
931 dlat_max = clat + 90;
932 }
933
934 if (std::isnan(dlon_min)) {
935 // if neither pole is visible, but left and right of the screen are in
936 // space we can avoid drawing the far side of the earth
937 if (dlat_max < 90 && dlat_min > -90) {
938 dlon_min =
939 clon - 90 -
940 fabs(clat); // this logic is not optimal, is it always correct?
941 dlon_max = clon + 90 + fabs(clat);
942 } else {
943 dlon_min = -180;
944 dlon_max = 180;
945 }
946 }
947 } break;
948
949 default: // works for mercator and equirectangular
950 {
951 GetLLFromPix(ul, &dlat_max, &dlon_min);
952 GetLLFromPix(lr, &dlat_min, &dlon_max);
953 }
954 }
955
956 if (clon < dlon_min)
957 dlon_min -= 360;
958 else if (clon > dlon_max)
959 dlon_max += 360;
960
961 // Set the viewport lat/lon bounding box appropriately
962 vpBBox.Set(dlat_min, dlon_min, dlat_max, dlon_max);
963
964 // Restore the rotation angle
965 SetRotationAngle(rotation_save);
966}
967
968void ViewPort::SetBBoxDirect(double latmin, double lonmin, double latmax,
969 double lonmax) {
970 vpBBox.Set(latmin, lonmin, latmax, lonmax);
971}
972bool ViewPort::ContainsIDL() {
973 if ((vpBBox.GetMinLon() <= -180.) && (vpBBox.GetMaxLon() > -180.))
974 return true;
975 if ((vpBBox.GetMinLon() <= 180.) && (vpBBox.GetMaxLon() > 180.)) return true;
976 return false;
977}
978
979ViewPort ViewPort::BuildExpandedVP(int width, int height) {
980 ViewPort new_vp = *this;
981
982 new_vp.pix_width = width;
983 new_vp.pix_height = height;
984 new_vp.SetBoxes();
985
986 return new_vp;
987}
988
989void ViewPort::SetVPTransformMatrix() {
990 mat4x4 m;
991 mat4x4_identity(m);
992 mat4x4_scale_aniso((float(*)[4])vp_matrix_transform, m,
993 2.0 / (float)pix_width, -2.0 / (float)pix_height, 1.0);
994 mat4x4_translate_in_place((float(*)[4])vp_matrix_transform, -pix_width / 2,
995 -pix_height / 2, 0);
996}
Class AisDecoder and helpers.
Class AISTargetAlertDialog and helpers.
Chart info panel.
Charts database management
BSB chart management.
Generic Chart canvas base.
An iterator class for OCPNRegion.
Definition OCPNRegion.h:156
A wrapper class for wxRegion with additional functionality.
Definition OCPNRegion.h:56
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:84
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:232
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:261
void SetBoxes(void)
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:825
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:242
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:135
wxRect GetVPRectIntersect(size_t n, float *llpoints)
Get the viewport rectangle intersecting with a set of lat/lon points.
Definition viewport.cpp:802
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:259
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:147
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:244
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:240
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:108
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:423
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:227
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:225
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:138
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:247
Class cm93chart and helpers – CM93 chart state.
Primary navigation console display for route and vessel tracking.
Multiplexer class and helpers.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.