LLRegion ViewPort::GetLLRegion( const OCPNRegion ®ion ) { // todo: for these projecetions, improve this calculation by using the // method in SetBoxes here #ifndef ocpnUSE_GL return LLRegion(GetBBox()); #else if(!glChartCanvas::CanClipViewport(*this)) return LLRegion(GetBBox()); OCPNRegionIterator it( region ); LLRegion r; while( it.HaveRects() ) { wxRect rect = it.GetRect(); int x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height; int p[8] = {x1, y1, x2, y1, x2, y2, x1, y2}; double pll[8]; for(int i=0; i<8; i+=2) GetLLFromPix(wxPoint(p[i], p[i+1]), pll+i, pll+i+1); // resolve (this works even if rectangle crosses both 0 and 180) //if(LLRegion::PointsCCW(4, pll)) for(int i=0; i<8; i+=2) { if(pll[i+1] <= clon - 180) pll[i+1] += 360; else if(pll[i+1] >= clon + 180) pll[i+1] -= 360; } r.Union(LLRegion(4, pll)); it.NextRect(); } return r; #endif }
void ViewPort::SetBoxes( void ) { // In the case where canvas rotation is applied, we need to define a larger "virtual" pixel window size to ensure that // enough chart data is fatched and available to fill the rotated screen. rv_rect = wxRect( 0, 0, pix_width, pix_height ); // Specify the minimum required rectangle in unrotated screen space which will supply full screen data after specified rotation if (( fabs( skew ) > .0001 ) || (fabs(rotation )>.0001 )) { double rotator = rotation; double lpixh = pix_height; double lpixw = pix_width; lpixh = wxMax(lpixh, (fabs(pix_height * cos(skew)) + fabs(pix_width * sin(skew)))); lpixw = wxMax(lpixw, (fabs(pix_width * cos(skew)) + fabs(pix_height * sin(skew)))); int dy = wxRound( fabs( lpixh * cos( rotator ) ) + fabs( lpixw * sin( rotator ) ) ); int dx = wxRound( fabs( lpixw * cos( rotator ) ) + fabs( lpixh * sin( rotator ) ) ); // It is important for MSW build that viewport pixel dimensions be multiples of 4..... if( dy % 4 ) dy += 4 - ( dy % 4 ); if( dx % 4 ) dx += 4 - ( dx % 4 ); int inflate_x = wxMax(( dx - pix_width ) / 2, 0); int inflate_y = wxMax(( dy - pix_height ) / 2, 0); // Grow the source rectangle appropriately rv_rect.Inflate( inflate_x, inflate_y ); } // Compute Viewport lat/lon reference points for co-ordinate hit testing // This must be done in unrotated space with respect to full unrotated screen space calculated above double rotation_save = rotation; SetRotationAngle(0.0); wxPoint ul( rv_rect.x, rv_rect.y ), lr( rv_rect.x + rv_rect.width, rv_rect.y + rv_rect.height ); double dlat_min, dlat_max, dlon_min, dlon_max; bool hourglass = false; switch(m_projection_type) { case PROJECTION_TRANSVERSE_MERCATOR: case PROJECTION_STEREOGRAPHIC: case PROJECTION_GNOMONIC: hourglass = true; case PROJECTION_POLYCONIC: case PROJECTION_POLAR: case PROJECTION_ORTHOGRAPHIC: { double d; if( clat > 0 ) { // north polar wxPoint u( rv_rect.x + rv_rect.width/2, rv_rect.y ); wxPoint ur( rv_rect.x + rv_rect.width, rv_rect.y ); GetLLFromPix( ul, &d, &dlon_min ); GetLLFromPix( ur, &d, &dlon_max ); GetLLFromPix( lr, &dlat_min, &d ); GetLLFromPix( u, &dlat_max, &d ); if(fabs(fabs(d - clon) - 180) < 1) { // the pole is onscreen dlat_max = 90; dlon_min = -180; dlon_max = 180; } else if(wxIsNaN(dlat_max)) dlat_max = 90; if(hourglass) { // near equator, center may be less wxPoint l( rv_rect.x + rv_rect.width/2, rv_rect.y + rv_rect.height ); double dlat_min2; GetLLFromPix( l, &dlat_min2, &d ); dlat_min = wxMin(dlat_min, dlat_min2); } if(wxIsNaN(dlat_min)) // world is off-screen dlat_min = clat - 90; } else { // south polar wxPoint l( rv_rect.x + rv_rect.width/2, rv_rect.y + rv_rect.height ); wxPoint ll( rv_rect.x, rv_rect.y + rv_rect.height ); GetLLFromPix( ul, &dlat_max, &d ); GetLLFromPix( lr, &d, &dlon_max ); GetLLFromPix( ll, &d, &dlon_min ); GetLLFromPix( l, &dlat_min, &d ); if(fabs(fabs(d - clon) - 180) < 1) { // the pole is onscreen dlat_min = -90; dlon_min = -180; dlon_max = 180; } else if(wxIsNaN(dlat_min)) dlat_min = -90; if(hourglass) { // near equator, center may be less wxPoint u( rv_rect.x + rv_rect.width/2, rv_rect.y ); double dlat_max2; GetLLFromPix( u, &dlat_max2, &d ); dlat_max = wxMax(dlat_max, dlat_max2); } if(wxIsNaN(dlat_max)) // world is off-screen dlat_max = clat + 90; } if(wxIsNaN(dlon_min)) { // if neither pole is visible, but left and right of the screen are in space // we can avoid drawing the far side of the earth if(dlat_max < 90 && dlat_min > -90) { dlon_min = clon - 90 - fabs(clat); // this logic is not optimal, is it always correct? dlon_max = clon + 90 + fabs(clat); } else { dlon_min = -180; dlon_max = 180; } } } break; default: // works for mercator and equirectangular { GetLLFromPix( ul, &dlat_max, &dlon_min ); GetLLFromPix( lr, &dlat_min, &dlon_max ); } } if( clon < dlon_min ) dlon_min -= 360; else if(clon > dlon_max) dlon_max += 360; // Set the viewport lat/lon bounding box appropriately vpBBox.Set( dlat_min, dlon_min, dlat_max, dlon_max ); // Restore the rotation angle SetRotationAngle( rotation_save ); }
OCPNRegion ViewPort::GetVPRegionIntersect( const OCPNRegion &Region, size_t nPoints, float *llpoints, int chart_native_scale, wxPoint *ppoints ) { // Calculate the intersection between a given OCPNRegion (Region) and a polygon specified by lat/lon points. // If the viewpoint is highly overzoomed wrt to chart native scale, the polygon region may be huge. // This can be very expensive, and lead to crashes on some platforms (gtk in particular) // So, look for this case and handle appropriately with respect to the given Region if( chart_scale < chart_native_scale / 10 ) { // Scan the points one-by-one, so that we can get min/max to make a bbox float *pfp = llpoints; float lon_max = -10000.; float lon_min = 10000.; float lat_max = -10000.; float lat_min = 10000.; for( unsigned int ip = 0; ip < nPoints; ip++ ) { lon_max = wxMax(lon_max, pfp[1]); lon_min = wxMin(lon_min, pfp[1]); lat_max = wxMax(lat_max, pfp[0]); lat_min = wxMin(lat_min, pfp[0]); pfp += 2; } LLBBox chart_box; chart_box.Set( lat_min, lon_min, lat_max, lon_max ); // Case: vpBBox is completely outside the chart box, or vice versa // Return an empty region if( chart_box.IntersectOut( vpBBox ) ) return OCPNRegion(); // Case: vpBBox is completely inside the chart box // Note that this test is not perfect, and will fail for some charts. // The chart coverage may be essentially triangular, and the viewport box // may be in the "cut off" segment of the chart_box, and not actually // exhibit any true overlap. Results will be reported incorrectly. // How to fix: maybe scrub the chart points and see if it is likely that // a region may be safely built and intersection tested. if( chart_box.IntersectIn( vpBBox ) ) return Region; wxPoint p1 = GetPixFromLL( lat_max, lon_min ); // upper left wxPoint p2 = GetPixFromLL( lat_min, lon_max ); // lower right OCPNRegion r( p1, p2 ); r.Intersect( Region ); return r; } // More "normal" case wxPoint *pp; // Use the passed point buffer if available if( ppoints == NULL ) pp = new wxPoint[nPoints]; else pp = ppoints; float *pfp = llpoints; wxPoint p = GetPixFromLL( pfp[0], pfp[1] ); int poly_x_max, poly_y_max, poly_x_min, poly_y_min; bool valid = false; for( unsigned int ip = 0; ip < nPoints; ip++ ) { wxPoint p = GetPixFromLL( pfp[0], pfp[1] ); pp[ip] = p; if(p.x == INVALID_COORD) continue; if(valid) { poly_x_max = wxMax(poly_x_max, p.x); poly_y_max = wxMax(poly_y_max, p.y); poly_x_min = wxMin(poly_x_min, p.x); poly_y_min = wxMin(poly_y_min, p.y); } else { poly_x_max = p.x; poly_y_max = p.y; poly_x_min = p.x; poly_y_min = p.y; valid = true; } pfp += 2; } if(!valid) { delete[] pp; return OCPNRegion(); //empty; } // We want to avoid processing regions with very large rectangle counts, // so make some tests for special cases float_2Dpt p0, p1, p2, p3; // First, calculate whether any segment of the input polygon intersects the specified Region int nrect = 0; bool b_intersect = false; OCPNRegionIterator screen_region_it1( Region ); while( screen_region_it1.HaveRects() ) { wxRect rect = screen_region_it1.GetRect(); double lat, lon; // The screen region corners GetLLFromPix( wxPoint(rect.x, rect.y), &lat, &lon ); p0.y = lat; p0.x = lon; GetLLFromPix( wxPoint(rect.x + rect.width, rect.y), &lat, &lon ); p1.y = lat; p1.x = lon; GetLLFromPix( wxPoint(rect.x + rect.width, rect.y + rect.height), &lat, &lon ); p2.y = lat; p2.x = lon; GetLLFromPix( wxPoint(rect.x, rect.y + rect.height), &lat, &lon ); p3.y = lat; p3.x = lon; for(size_t i=0 ; i < nPoints-1 ; i++) { // Quick check on y dimension int y0 = pp[i].y; int y1 = pp[i+1].y; if(y0 == INVALID_COORD || y1 == INVALID_COORD) continue; if( ((y0 < rect.y) && (y1 < rect.y)) || ((y0 > rect.y+rect.height) && (y1 > rect.y+rect.height)) ) continue; // both ends of line outside of box, top or bottom // Look harder float_2Dpt f0; f0.y = llpoints[i * 2]; f0.x = llpoints[(i * 2) + 1]; float_2Dpt f1; f1.y = llpoints[(i+1) * 2]; f1.x = llpoints[((i+1) * 2) + 1]; b_intersect |= Intersect_FL( p0, p1, f0, f1) != 0; if(b_intersect) break; b_intersect |= Intersect_FL( p1, p2, f0, f1) != 0; if(b_intersect) break; b_intersect |= Intersect_FL( p2, p3, f0, f1) != 0; if(b_intersect) break; b_intersect |= Intersect_FL( p3, p0, f0, f1) != 0; if(b_intersect) break; // Must check the case where the input polygon has been pre-normalized, eg (0 < lon < 360), as cm93 f0.x -= 360.; f1.x -= 360.; b_intersect |= Intersect_FL( p0, p1, f0, f1) != 0; if(b_intersect) break; b_intersect |= Intersect_FL( p1, p2, f0, f1) != 0; if(b_intersect) break; b_intersect |= Intersect_FL( p2, p3, f0, f1) != 0; if(b_intersect) break; b_intersect |= Intersect_FL( p3, p0, f0, f1) != 0; if(b_intersect) break; } // Check segment, last point back to first point if(!b_intersect) { float_2Dpt f0; f0.y = llpoints[(nPoints-1) * 2]; f0.x = llpoints[((nPoints-1) * 2) + 1]; float_2Dpt f1; f1.y = llpoints[0]; f1.x = llpoints[1]; b_intersect |= Intersect_FL( p0, p1, f0, f1) != 0; b_intersect |= Intersect_FL( p1, p2, f0, f1) != 0; b_intersect |= Intersect_FL( p2, p3, f0, f1) != 0; b_intersect |= Intersect_FL( p3, p0, f0, f1) != 0; f0.x -= 360.; f1.x -= 360.; b_intersect |= Intersect_FL( p0, p1, f0, f1) != 0; b_intersect |= Intersect_FL( p1, p2, f0, f1) != 0; b_intersect |= Intersect_FL( p2, p3, f0, f1) != 0; b_intersect |= Intersect_FL( p3, p0, f0, f1) != 0; } screen_region_it1.NextRect(); nrect++; } // If there is no itersection, we need to consider the case where // the subject polygon is entirely within the Region bool b_contained = false; if(!b_intersect) { OCPNRegionIterator screen_region_it2( Region ); while( screen_region_it2.HaveRects() ) { wxRect rect = screen_region_it2.GetRect(); for(size_t i=0 ; i < nPoints-1 ; i++) { int x0 = pp[i].x; int y0 = pp[i].y; if(x0 == INVALID_COORD) continue; if((x0 < rect.x) || (x0 > rect.x+rect.width)) continue; if((y0 < rect.y) || (y0 > rect.y+rect.height)) continue; b_contained = true; break; } screen_region_it2.NextRect(); } } #if 1 // and here is the payoff if(!b_contained && !b_intersect) { // Two cases to consider wxRect rpoly( poly_x_min, poly_y_min, poly_x_max - poly_x_min , poly_y_max - poly_y_min); wxRect rRegion = Region.GetBox(); if(rpoly.Contains(rRegion)) { // subject poygon may be large enough to fully encompass the target Region, // but it might not, especially for irregular or concave charts. // So we cannot directly shortcut here // Better check.... #if 1 if(nrect == 1) { // most common case // If the subject polygon contains the center of the target rectangle, then // the intersection must be the target rectangle float rlat = (p0.y + p2.y)/2.; float rlon = (p0.x + p1.x)/2.; if(G_PtInPolygon_FL((float_2Dpt *)llpoints, nPoints, rlon, rlat)) { if( NULL == ppoints ) delete[] pp; return Region; } rlon += 360.; if(G_PtInPolygon_FL((float_2Dpt *)llpoints, nPoints, rlon, rlat)) { if( NULL == ppoints ) delete[] pp; return Region; } // otherwise, there is no intersection else { if( NULL == ppoints ) delete[] pp; wxRegion r; return r; } } #endif } else { // Subject polygon is entirely outside of target Region // so the intersection must be empty. if( NULL == ppoints ) delete[] pp; wxRegion r; return r; } } else if(b_contained && !b_intersect) { // subject polygon is entirely withing the target Region, // so the intersection is the subject polygon OCPNRegion r = OCPNRegion( nPoints, pp ); if( NULL == ppoints ) delete[] pp; return r; } #endif #ifdef __WXGTK__ sigaction(SIGSEGV, NULL, &sa_all_old); // save existing action for this signal struct sigaction temp; sigaction(SIGSEGV, NULL, &temp);// inspect existing action for this signal temp.sa_handler = catch_signals;// point to my handler sigemptyset(&temp.sa_mask);// make the blocking set // empty, so that all // other signals will be // unblocked during my handler temp.sa_flags = 0; sigaction(SIGSEGV, &temp, NULL); if(sigsetjmp(env, 1))// Something in the below code block faulted.... { sigaction(SIGSEGV, &sa_all_old, NULL); // reset signal handler return Region; } else { OCPNRegion r = OCPNRegion(nPoints, pp); if(NULL == ppoints) delete[] pp; sigaction(SIGSEGV, &sa_all_old, NULL); // reset signal handler r.Intersect(Region); return r; } #else OCPNRegion r = OCPNRegion( nPoints, pp ); if( NULL == ppoints ) delete[] pp; r.Intersect( Region ); return r; #endif }
LLRegion ViewPort::GetLLRegion( const OCPNRegion ®ion ) { // todo: for these projecetions, improve this calculation by using the // method in SetBoxes here #ifndef ocpnUSE_GL return LLRegion(GetBBox()); #else if(!glChartCanvas::CanClipViewport(*this)) return LLRegion(GetBBox()); OCPNRegionIterator it( region ); LLRegion r; while( it.HaveRects() ) { wxRect rect = it.GetRect(); int x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height; int p[8] = {x1, y1, x2, y1, x2, y2, x1, y2}; double pll[540]; int j; /* if the viewport is rotated, we must split the segments as straight lines in lat/lon coordinates map to curves in projected coordinate space */ if(fabs( rotation ) >= 0.0001) { j=0; double lastlat, lastlon; int li = 6; GetLLFromPix(wxPoint(p[li], p[li+1]), &lastlat, &lastlon); for(int i=0; i<8; i+=2) { double lat, lon; GetLLFromPix(wxPoint(p[i], p[i+1]), &lat, &lon); // use 2 degree grid double grid = 2; int lat_splits = floor(fabs(lat-lastlat) / grid); double lond = fabs(lon-lastlon); int lon_splits = floor((lond > 180 ? 360-lond : lond) / grid); int splits = wxMax(lat_splits, lon_splits) + 1; for(int k = 1; k<splits; k++) { float d = (float)k / splits; GetLLFromPix(wxPoint((1-d)*p[li] + d*p[i], (1-d)*p[li+1] + d*p[i+1]), pll+j, pll+j+1); j += 2; } pll[j++] = lat; pll[j++] = lon; li = i; lastlat = lat, lastlon = lon; } } else { j=8; for(int i=0; i<j; i+=2) GetLLFromPix(wxPoint(p[i], p[i+1]), pll+i, pll+i+1); } // resolve (this works even if rectangle crosses both 0 and 180) for(int i=0; i<j; i+=2) { if(pll[i+1] <= clon - 180) pll[i+1] += 360; else if(pll[i+1] >= clon + 180) pll[i+1] -= 360; } r.Union(LLRegion(j/2, pll)); it.NextRect(); } return r; #endif }
void ViewPort::SetBoxes( void ) { // In the case where canvas rotation is applied, we need to define a larger "virtual" pixel window size to ensure that // enough chart data is fatched and available to fill the rotated screen. rv_rect = wxRect( 0, 0, pix_width, pix_height ); // Specify the minimum required rectangle in unrotated screen space which will supply full screen data after specified rotation if( ( g_bskew_comp && ( fabs( skew ) > .001 ) ) || ( fabs( rotation ) > .001 ) ) { double rotator = rotation; if(g_bskew_comp) rotator -= skew; int dy = wxRound( fabs( pix_height * cos( rotator ) ) + fabs( pix_width * sin( rotator ) ) ); int dx = wxRound( fabs( pix_width * cos( rotator ) ) + fabs( pix_height * sin( rotator ) ) ); // It is important for MSW build that viewport pixel dimensions be multiples of 4..... if( dy % 4 ) dy += 4 - ( dy % 4 ); if( dx % 4 ) dx += 4 - ( dx % 4 ); int inflate_x = wxMax(( dx - pix_width ) / 2, 0); int inflate_y = wxMax(( dy - pix_height ) / 2, 0); // Grow the source rectangle appropriately if( fabs( rotator ) > .001 ) rv_rect.Inflate( inflate_x, inflate_y ); } // Compute Viewport lat/lon reference points for co-ordinate hit testing // This must be done in unrotated space with respect to full unrotated screen space calculated above double rotation_save = rotation; SetRotationAngle( 0. ); double lat_ul, lat_ur, lat_lr, lat_ll; double lon_ul, lon_ur, lon_lr, lon_ll; GetLLFromPix( wxPoint( rv_rect.x, rv_rect.y ), &lat_ul, &lon_ul ); GetLLFromPix( wxPoint( rv_rect.x + rv_rect.width, rv_rect.y ), &lat_ur, &lon_ur ); GetLLFromPix( wxPoint( rv_rect.x + rv_rect.width, rv_rect.y + rv_rect.height ), &lat_lr, &lon_lr ); GetLLFromPix( wxPoint( rv_rect.x, rv_rect.y + rv_rect.height ), &lat_ll, &lon_ll ); if( clon < 0. ) { if( ( lon_ul > 0. ) && ( lon_ur < 0. ) ) { lon_ul -= 360.; lon_ll -= 360.; } } else { if( ( lon_ul > 0. ) && ( lon_ur < 0. ) ) { lon_ur += 360.; lon_lr += 360.; } } if( lon_ur < lon_ul ) { lon_ur += 360.; lon_lr += 360.; } if( lon_ur > 360. ) { lon_ur -= 360.; lon_lr -= 360.; lon_ul -= 360.; lon_ll -= 360.; } double dlat_min = lat_ul; dlat_min = fmin ( dlat_min, lat_ur ); dlat_min = fmin ( dlat_min, lat_lr ); dlat_min = fmin ( dlat_min, lat_ll ); double dlon_min = lon_ul; dlon_min = fmin ( dlon_min, lon_ur ); dlon_min = fmin ( dlon_min, lon_lr ); dlon_min = fmin ( dlon_min, lon_ll ); double dlat_max = lat_ul; dlat_max = fmax ( dlat_max, lat_ur ); dlat_max = fmax ( dlat_max, lat_lr ); dlat_max = fmax ( dlat_max, lat_ll ); double dlon_max = lon_ur; dlon_max = fmax ( dlon_max, lon_ul ); dlon_max = fmax ( dlon_max, lon_lr ); dlon_max = fmax ( dlon_max, lon_ll ); // Set the viewport lat/lon bounding box appropriately vpBBox.SetMin( dlon_min, dlat_min ); vpBBox.SetMax( dlon_max, dlat_max ); // Restore the rotation angle SetRotationAngle( rotation_save ); }