[Gdal-dev] GDAL & OpenEV / MFC

Chapman, Martin MChapman at sanz.com
Sat Jan 15 19:32:32 EST 2005


James,
 
I've done a lot of exactly that.  Here are some things I've learned.  There are also code snippets below.
 
1.  Only use OpenGL if you want to render 3D data like elevation data (DEM, DTED, ...) in 3D meshes.  OpenGL is awesome for 3D but slow (comparably) if you only have X,Y (2D).  It's good for cross-platform rendering, but if you're using MFC you should go with the latest native graphics device (GDI+).  In my applications I use the MDI application framework and have different Documents/Views for 2D, 3D, and raw images (rasters).  
 
2.  To display vector maps with projected imagery (rasters that are scaled with the vectors in the same view) use GDI+.  This is hands down the best and fastest way to draw vectors (lat/lon) on Windows.  The old style GDI only uses integers for coordinates so you can't easily display lat/lon values.  OpenGL has to do a bunch of extra math when handling shapes other than triangles because the shape could be non-coplanar if it has more than 3 coords.  Also, OpenGL has an archaic interface for working with the graphics pipeline.  GDI+ on the other hand is almost identical to the Java2D library and is a totally awesome API that was extremely well thought out and is very sophistocated, and it's OO where OpenGL API is not.  We even use Java2D to render from OGR at work and it's very fast.  Not as fast as GDI+ though.  
 
3.  To display "raw" rasters (projected or unprojected), use the old style GDI and Device Context functions like BitBlt and StretchBlt or GDI+.  Nothing matches the speed of the old GDI funtions for rasters though.  GDI+ also handles raw rasters extremely well.  In fact, from an API perspective, it is far superior for rasters than GDI, but GDI is just fast.  You can use OpenGL for rasters as well, but I think their raster support is weak, unless you want to texture map an image to a 3D mesh, then it rocks.  
 
4.  No matter what graphics library you use, your code will be slow if you don't carefully think about how you load and draw your data.  Whether it's raster or vector data, you should always, load only enough data to fill your view.  
    a. Loading the whole file works great for small files, but will bring your app (and any computer) to it's knees when you start loading big files, or lots of small files.  
    b. Do not, cache geometries or pixel data unless you give your users an option to cache at will, because you will quickly consume all the users available memory.  Instead, each time your app pans or zooms, load the data again from the file, using a rectangle or quad index for vectors and an x,y,width,height offset for rasters.  
    c. Limit the number of times you loop through the features with OGR to one time per onpaint event.  That means, don't use the GetWKB functions.  Frank has to loop through the points and build the WKB first, then you will have to loop through the points again to draw them.  If you elimanate that loop then you will be twice as fast.
    d.  Don't store points.  That means, get them from OGR, stick it into a GDI+ point object array, draw it, and then get rid of it.  
    e. AND THE MOST IMPORTANT RULE OF RENDERING IS....limit the number of times you draw the points to the screen.  I see a lot of code where the developer is drawing every point to the screen as they loop through the features.  Drawing to a screen moves millions of bits around and is very very expensive and humans can only process a screen update visually something like 30 screen refreshes a second, so drawing between those intervals is useless for any lifeform that I know of that usues a computer.  You should do something like time based rendering or once per feature.  You should accumulate points using the GDI+ PointF class in a PointF[] and then draw them periodically and then delete them.  MFC has a nice handy function called GetTickCount() that will help you track time in milli-seconds to do this.  It also gives you a very snooth rendering appearance.  
 
The bottom line is that different graphics libraries are better suited for different jobs on different hardware.  So before you pick one, really think about wht you want to do, and then be smart about NOT trying to make one document/view to handle all situations.  
 
I have tons of code using different graphics libraries so you will have to ask me for specific examples if you want them.  Listed below is a code snippet for using GDI+ and vectors using OGR.  It uses some of my own classes to store feature style info and cached graphics for when a user wants to cache and un-cache layers.  It also, uses a second GDI+ Graphics object to draw to a CachedBitmap for double buffering.  Really, double-buffering doesn't work well for vectors because it can take a while to draw really large vector files (hundreds of megabytes and features), but is very useful for smooth panning.  The code below draws points to the screen first to satisfy the use, and then loops again to paint to an offscreen bitmap for smooth/quick panning.  If you want to double the speed of loading, then remove the pMemGraphics and m_spMemGraphics object from the functions.  Although, this will not change the visual appearance because I already draw first, but it will finish the over-all loading faster beacuse you are drawing half as much.  
 
The interesting functions below are OnPaint() and DrawVectorLayer().
 
Have fun!
Martin Chapman
void CMapView::OnPaint()

{

try

{

CPaintDC dc(this);

CRect clientRect;

GetClientRect(&clientRect);

Graphics graphics(dc.m_hDC);

CMainFrame* pMainFrame = (CMainFrame*) AfxGetMainWnd();

NAVIGATIONMODE eNavigationMode = pMainFrame->GetMapNavigationMode();

CVPPtr< IVPMap> spMap = GetMap();

CVPPtr< IVPEnvelope> spBBox = spMap->GetBoundingBox();

m_nScaleX = (REAL) (spMap->GetSRSWidth() / spBBox->GetWidth()); 

m_nScaleY = (REAL) (spMap->GetSRSHeight() / spBBox->GetHeight()); 

REAL nClientXFactor = (REAL) (spMap->GetSRSWidth() / clientRect.Width()); 

REAL nClientYFactor = (REAL) (spMap->GetSRSHeight() / clientRect.Height()); 

REAL nClientScaleFactor = nClientXFactor > nClientYFactor ? nClientXFactor : nClientYFactor;

m_nScaleX /= nClientScaleFactor;

m_nScaleY /= nClientScaleFactor;

m_nTranslationX = (REAL) (spBBox->GetMinX() + (spBBox->GetWidth() / 2));

m_nTranslationY = (REAL) (spBBox->GetMinY() + (spBBox->GetHeight() / 2));

m_nViewportOriginX = (REAL) (((clientRect.Width() / 2) / m_nScaleX));

m_nViewportOriginY = (REAL) (((clientRect.Height() / 2) / m_nScaleY));

graphics.SetSmoothingMode(SmoothingModeHighSpeed);

graphics.SetCompositingQuality(CompositingQualityHighSpeed);

if (m_bZooming && (eNavigationMode == NAVIGATION_MODE_PANMOVE))

{

SolidBrush solidBrush(Color(255, 255, 255, 255));

RectF clearRect((REAL) clientRect.left, (REAL) clientRect.top, 

(REAL) clientRect.Width(), (REAL) clientRect.Height());

RectF excludeRect((REAL)-m_nStrokeOffSetX, (REAL) m_nStrokeOffSetY, 

(REAL)clientRect.Width(), (REAL) clientRect.Height());

Region clearRegion(clearRect);

clearRegion.Exclude(excludeRect);

graphics.FillRegion(&solidBrush, &clearRegion);

graphics.TranslateTransform(-m_nStrokeOffSetX, m_nStrokeOffSetY);

}

if (!m_pMemBitmap)

CreateOffScreenGraphics(clientRect.Width(), clientRect.Height(), &graphics);

if (((eNavigationMode != NAVIGATION_MODE_ZOOMIN_BAND) &&

(eNavigationMode != NAVIGATION_MODE_ZOOMOUT_BAND)) || !m_bZooming)

{

graphics.DrawCachedBitmap(m_pCachedBitmap, 0, 0);

}

}

catch(exception e) 

{AfxMessageBox(e.what());}

catch(char* e) 

{AfxMessageBox(e);}

catch(...) 

{AfxMessageBox("An unexpected error occurred in CMapView::OnPaint().");}

}

void CMapView::CreateOffScreenGraphics(int nWidth, int nHeight, Graphics* pGraphics)

{

try

{

#ifdef _DEBUG

#undef new

m_pMemBitmap = new Bitmap(nWidth, nHeight, pGraphics);

#define new DEBUG_NEW

#else

m_pMemBitmap = new Bitmap(nWidth, nHeight, pGraphics);

#endif

CVPPtr< IVPMap> spMap = GetMap();

m_pMemGraphics = Graphics::FromImage(m_pMemBitmap);

m_pMemGraphics->TranslateTransform(m_nViewportOriginX - (m_nTranslationX), m_nViewportOriginY + (m_nTranslationY));

m_pMemGraphics->ScaleTransform(m_nScaleX, m_nScaleY, MatrixOrderAppend);

m_pMemGraphics->Clear(Color(255, 255, 255, 255));

DrawMap(pGraphics, m_pMemGraphics, spMap);

#ifdef _DEBUG

#undef new

m_pCachedBitmap = new CachedBitmap(m_pMemBitmap, pGraphics);

#define new DEBUG_NEW

#else

m_pCachedBitmap = new CachedBitmap(m_pMemBitmap, pGraphics);

#endif

}

catch(exception e) 

{throw e;}

catch(char* e) 

{throw e;}

catch(...) 

{throw "An unexpected error occurred in CMapView::CreateOffScreenGraphics().";}

}

void CMapView::DrawMap(Graphics* pGraphics, Graphics* pMemGraphics, IVPMap* pMap) 

{

try

{

if (!pMap) return;

CVPPtr< IVPMap> spMap = pMap;

double nScale = spMap->GetScale();

pGraphics->TranslateTransform(m_nViewportOriginX - (m_nTranslationX), m_nViewportOriginY + (m_nTranslationY));

pGraphics->ScaleTransform(m_nScaleX, m_nScaleY, MatrixOrderAppend);

for (int j = spMap->GetLayerCount() - 1; j >= 0; j--)

{

CVPPtr< IVPLayer> spLayer = spMap->GetLayerAt(j);

if (spLayer->IsVisible())

{

if (((nScale >= spLayer->GetMinScale()) && 

(nScale < spLayer->GetMaxScale())) ||

spLayer->IsShowAllScales())

{

DrawLayer(pGraphics, pMemGraphics, spLayer);

}

}

pGraphics->TranslateTransform(m_nViewportOriginX + (m_nTranslationX), m_nViewportOriginY - (m_nTranslationY));

pGraphics->ScaleTransform(-m_nScaleX, -m_nScaleY, MatrixOrderAppend);

}

catch(exception e) 

{throw e;}

catch(char* e) 

{throw e;}

catch(...) 

{throw "An unexpected error occurred in CMapView::DrawMap().";}

}

void CMapView::DrawLayer(Graphics* pGraphics, Graphics* pMemGraphics, IVPLayer* pLayer) 

{

try

{

if (dynamic_cast< IVPLayerCollection*>(pLayer))

{

list< IVPLayer*>::iterator it;

list< IVPLayer*>* pList = ((IVPLayerCollection*)pLayer)->GetLayers();

for (it = pList->begin(); it != pList->end(); it++)

DrawLayer(pGraphics, pMemGraphics, *it);

}

else if (dynamic_cast< IVPLayer*>(pLayer))

{

if (dynamic_cast< IVPVectorLayer*> (pLayer))

{

DrawVectorLayer(pGraphics, pMemGraphics, (IVPVectorLayer*) pLayer);

}

}

catch(exception e) 

{throw e;}

catch(char* e) 

{throw e;}

catch(...) 

{throw "An unexpected error occurred in CMapView::DrawLayer().";}

}

void CMapView::DrawVectorLayer(Graphics* pGraphics, 

Graphics* pMemGraphics, 

IVPVectorLayer* pVectorLayer)

{

try

{

CVPPtr< IVPVectorLayer> spVectorLayer = pVectorLayer;

CVPPtr< IVPFeatureStyle> spFeatureStyle = spVectorLayer->GetFeatureStyle();

CVPPtr< IVPGraphicCollection> spGraphicCollection = pVectorLayer->GetGraphicCollection();

bool bIsCached = spVectorLayer->IsCached();

if (bIsCached && spGraphicCollection.p)

{

IVPGraphic* pGraphic = NULL;

GraphicsPath* pPath = NULL;

list< IVPGraphic*>::reverse_iterator it;

list< IVPGraphic*>* pGraphicList = spGraphicCollection->GetGraphics();

Pen* pPen = spGraphicCollection->GetPen();

if (pPen) pPen->SetWidth(spFeatureStyle->GetLineSize() / ((m_nScaleX + m_nScaleY) / 2.0f));

Brush* pBrush = spGraphicCollection->GetBrush();

for (it = pGraphicList->rbegin(); it != pGraphicList->rend(); it++)

{

pGraphic = (IVPGraphic*) *it;

pPath = (GraphicsPath*) pGraphic->GetPath();

if (pPath) 

{

if (pBrush) pMemGraphics->FillPath(pBrush, pPath);

if (pPen) pMemGraphics->DrawPath(pPen, pPath);

}

}

if (spVectorLayer->IsShowLabels())

DrawLabels(pGraphics, pMemGraphics, pLayer);

return;

}

int i = 0, j = 0, n = 0;

int nNumPoints = 0;

int nNumRings = 0;

int nNumGeometries = 0;

GraphicsPath* pPath = NULL;

OGRGeometry* pOgrGeometry = NULL;

OGRPoint* pPoint = NULL;

OGRLineString* pLineString = NULL;

OGRLinearRing* pLinearRing = NULL;

OGRMultiPoint* pMultiPoint = NULL;

OGRPolygon* pPolygon = NULL;

OGRMultiPolygon* pMultiPolygon = NULL;

OGRMultiLineString* pMultiLineString = NULL;

CVPPtr< IVPVectorSet> spVectorSet = (IVPVectorSet*) spVectorLayer->GetResultSet();

CVPPtr< IVPVectorGraphic> spVectorGraphic;

if (bIsCached)

spGraphicCollection = new CVPGraphicCollection();

int r = 0, g = 0, b = 0, a = 255;

float nLineSize = spFeatureStyle->GetLineSize();

float nPointSize = nLineSize / m_nScaleY * 4;

USES_CONVERSION;

WCHAR* sFontFace = T2W(spFeatureStyle->GetFontFace().c_str());

float nFontSize = spFeatureStyle->GetFontSize();

Font font(sFontFace, nFontSize);

RectF pointRect(0.0f, 0.0f, nPointSize, nPointSize);

PointF* pPoints = NULL;

r = spFeatureStyle->GetLineColor()->GetRed();

g = spFeatureStyle->GetLineColor()->GetGreen();

b = spFeatureStyle->GetLineColor()->GetBlue();

a = spFeatureStyle->GetLineColor()->GetAlpha();

Pen pen(Color(a, r, g, b), nLineSize / ((m_nScaleX + m_nScaleY) / 2.0f));

r = spFeatureStyle->GetFillColor()->GetRed();

g = spFeatureStyle->GetFillColor()->GetGreen();

b = spFeatureStyle->GetFillColor()->GetBlue();

a = spFeatureStyle->GetFillColor()->GetAlpha();

SolidBrush fillBrush(Color(a, r, g, b));

r = spFeatureStyle->GetFontColor()->GetRed();

g = spFeatureStyle->GetFontColor()->GetGreen();

b = spFeatureStyle->GetFontColor()->GetBlue();

a = spFeatureStyle->GetFontColor()->GetAlpha();

SolidBrush fontBrush(Color(a, r, g, b));

CRect clientRect;

GetClientRect(&clientRect);

if (bIsCached)

spVectorSet->SetSpatialFilter(NULL);

else

{

CVPPtr< IVPEnvelope> spBoundingBox = GetMap()->GetBoundingBox();

CVPPtr< IVPPoint> spPoint;

spPoint.Attach(spBoundingBox->GetCentroid());

double nMinX = spPoint->GetX() - (clientRect.Width() / 2) / m_nScaleX;

double nMaxY = spPoint->GetY() + (clientRect.Height() / 2) / m_nScaleY;

double nMaxX = spPoint->GetX() + (clientRect.Width() / 2) / m_nScaleX;

double nMinY = spPoint->GetY() - (clientRect.Height() / 2) / m_nScaleY;

CVPPtr< IVPEnvelope> spEnvelope = new CVPEnvelope(nMinX, nMinY, nMaxX, nMaxY);

spVectorSet->SetSpatialFilter(spEnvelope.Detach());

}

OGRLayer* pOgrLayer = (OGRLayer*) spVectorSet->GetLayerRef();

OGRwkbGeometryType eGeomType = wkbUnknown;

OGRFeature* pOgrFeature = pOgrLayer->GetNextFeature();

pGraphics->Clear(Color(255, 255, 255, 255));

while (pOgrFeature)

{

#ifdef _DEBUG

#undef new

pPath = new GraphicsPath();

#define new DEBUG_NEW

#else

pPath = new GraphicsPath();

#endif

pOgrGeometry = (OGRGeometry*) pOgrFeature->GetGeometryRef();

eGeomType = pOgrGeometry->getGeometryType();

if (eGeomType == wkbPoint)

{

pPoint = (OGRPoint*) pOgrGeometry;

if (nLineSize > 0)

{

pointRect.X = (REAL) pPoint->getX();

pointRect.Y = (REAL) -pPoint->getY();

pPath->AddRectangle(pointRect);

}

}

else if (eGeomType == wkbLineString)

{

pLineString = (OGRLineString*) pOgrGeometry;

nNumPoints = pLineString->getNumPoints();

pPoints = new PointF[nNumPoints];

pPath->StartFigure();

for (n = 0; n < nNumPoints; n++) 

pPoints[n] = PointF((REAL) pLineString->getX(n), (REAL) -pLineString->getY(n));

pPath->AddLines(pPoints, nNumPoints);

if (pPoints) delete [] pPoints;

}

else if (eGeomType == wkbPolygon) 

{

nNumRings = ((OGRPolygon*) pOgrGeometry)->getNumInteriorRings() + 1;

for (i = 0; i < nNumRings; i++)

{

if (i == 0)

pLinearRing = ((OGRPolygon*) pOgrGeometry)->getExteriorRing();

else

pLinearRing = ((OGRPolygon*) pOgrGeometry)->getInteriorRing(i - 1);

nNumPoints = pLinearRing->getNumPoints();

pPoints = new PointF[nNumPoints];

pPath->StartFigure();

if (i == 0)

{

for (n = 0; n < nNumPoints; n++) 

pPoints[n] = PointF((REAL) pLinearRing->getX(n), (REAL) -pLinearRing->getY(n));

}

else

{

for (n = nNumPoints - 1; n >= 0; n--) 

pPoints[n] = PointF((REAL) pLinearRing->getX(n), (REAL) -pLinearRing->getY(n));

}

pPath->CloseFigure();

pPath->AddPolygon(pPoints, nNumPoints);

if (pPoints) delete [] pPoints;

}

}

else if (eGeomType == wkbMultiPoint)

{

pMultiPoint = (OGRMultiPoint*) pOgrGeometry;

nNumGeometries = pMultiPoint->getNumGeometries();

for (i = 0; i < nNumGeometries; i++)

{

pPoint = (OGRPoint*) pMultiPoint->getGeometryRef(i);

if (nLineSize > 0)

{

pointRect.X = (REAL) pPoint->getX();

pointRect.Y = (REAL) -pPoint->getY();

pPath->AddRectangle(pointRect);

}

}

}

else if (eGeomType == wkbMultiLineString)

{

pMultiLineString = (OGRMultiLineString*) pOgrGeometry;

nNumGeometries = pMultiLineString->getNumGeometries();

for (i = 0; i < nNumGeometries; i++)

{

pLineString = (OGRLineString*) pMultiLineString->getGeometryRef(i);

nNumPoints = pLineString->getNumPoints();

pPoints = new PointF[nNumPoints];

pPath->StartFigure();

for (n = 0; n < nNumPoints; n++) 

pPoints[n] = PointF((REAL) pLineString->getX(n), (REAL) -pLineString->getY(n));

pPath->AddLines(pPoints, nNumPoints);

if (pPoints) delete [] pPoints;

}

}

else if (eGeomType == wkbMultiPolygon)

{

pMultiPolygon = (OGRMultiPolygon*) pOgrGeometry;

nNumGeometries = pMultiPolygon->getNumGeometries();

for (i = 0; i < nNumGeometries; i++)

{

pPolygon = (OGRPolygon*) pMultiPolygon->getGeometryRef(i);

nNumRings = ((OGRPolygon*) pPolygon)->getNumInteriorRings() + 1;

for (j = 0; j < nNumRings; j++)

{

if (j == 0)

pLinearRing = pPolygon->getExteriorRing();

else

pLinearRing = pPolygon->getInteriorRing(j - 1);

nNumPoints = pLinearRing->getNumPoints();

pPoints = new PointF[nNumPoints];

pPath->StartFigure();

if (i == 0)

{

for (n = 0; n < nNumPoints; n++) 

pPoints[n] = PointF((REAL) pLinearRing->getX(n), (REAL) -pLinearRing->getY(n));

}

else

{

for (n = nNumPoints - 1; n >= 0; n--) 

pPoints[n] = PointF((REAL) pLinearRing->getX(n), (REAL) -pLinearRing->getY(n));

}

pPath->CloseFigure();

pPath->AddPolygon(pPoints, nNumPoints);

if (pPoints) delete [] pPoints;

}

}

}

if (eGeomType != wkbLineString && eGeomType != wkbMultiLineString)

{

pGraphics->FillPath(&fillBrush, pPath);

pGraphics->Flush();

}

pGraphics->DrawPath(&pen, pPath);

pGraphics->Flush();

if (bIsCached)

{

spVectorGraphic = new CVPVectorGraphic();

spVectorGraphic->SetPath((void*) pPath);

spGraphicCollection->Add(spVectorGraphic.Detach());

}

else

delete pPath;

delete pOgrFeature;

pOgrFeature = pOgrLayer->GetNextFeature();

}

pOgrLayer->ResetReading();

IVPGraphic* pGraphic = NULL;

GraphicsPath* pCurrentPath = NULL;

list< IVPGraphic*>::reverse_iterator it;

list< IVPGraphic*>* pGraphicList = spGraphicCollection->GetGraphics();

for (it = pGraphicList->rbegin(); it != pGraphicList->rend(); it++)

{

pGraphic = (IVPGraphic*) *it;

pCurrentPath = (GraphicsPath*) pGraphic->GetPath();

if (pPath) 

{

pMemGraphics->FillPath(&fillBrush, pPath);

pMemGraphics->DrawPath(&pen, pPath);

}

}

if (bIsCached)

{

spGraphicCollection->SetPen(pen.Clone());

spGraphicCollection->SetBrush(fillBrush.Clone());

spGraphicCollection->SetFont(font.Clone());

spGraphicCollection->SetFontBrush(fontBrush.Clone());

spVectorLayer->SetGraphicCollection(spGraphicCollection.Detach());

}

}

catch(exception e) 

{throw e;}

catch(char* e) 

{throw e;}

catch(...) 

{throw "An unexpected error occurred in CMapView::DrawVectorLayer().";}

}

 
 
 
 
 
-----Original Message----- 
From: James Goodwin [mailto:James.Goodwin at sea.co.uk] 
Sent: Fri 1/14/2005 8:11 AM 
To: gdal-dev at xserve.flids.com 
Cc: 
Subject: [Gdal-dev] GDAL & OpenEV / MFC



	I've downloaded the GDAL library and been successfully built it and loaded an S-57 file.  I've also downloaded the OpenEV code for displaying S-57 files.
	
	However, I'd like to use GDAL, OpenGL and the MFC libraries (not GTK) to do my coding!
	
	Has anyone taken this path already?  If so, is the any free code available?
	
	James
	
	
	________________________________________________________________________
	This email, and any attachment, may contain information that is confidential and/or subject to copyright. The recipient of any email from SEA shall not forward any message or attachment to any third party without prior consent from SEA. Although SEA uses all reasonable endeavours to prevent the transmission of known viruses, SEA shall accept no liability for any damage to systems or loss of or corruption of data caused by any virus inadvertently transmitted. SEA reserves the right to monitor all incoming and outgoing email for any lawful purpose; this is clearly stated in the SEA policy - The Use of Computer and Telephony Equipment By Staff. The contents of this email do not give rise to any binding legal obligation upon SEA unless a separate confirmation is sent on headed stationery, either in hardcopy or by email, as an attachment. If an email is to create a binding obligation on SEA, through the use of an attachment, then this shall be clearly stated in the body text. Anyone dealing with SEA by email who objects to any of these terms should refer their concerns to the Business Systems Manager through the contact listed above.
	_______________________________________________
	Gdal-dev mailing list
	Gdal-dev at xserve.flids.com
	http://xserve.flids.com/mailman/listinfo/gdal-dev
	



More information about the Gdal-dev mailing list