Introduction
This is a continuation to my previous article for rendering shapefiles in OpenGL environment. Shapefiles have either to be point, line or polygon types. The main focus of this tip is to render polygons and fill them.This revision will also handle polygons with holes i.e, Donut Polygons.
Background
Polygons in shapefiles are organized in a typical manner.Each Polygon element may have single or multiple parts.Each part is called a ring.The rings have vertices which are arranged in clockwise or counter clockwise based on whether they represent outer ring or inner ring. Shapelib
library helps us to parse this special structure. The detailed description of this structure can be found in ESRI Shapefile Technical Description.
OpenGL cannot render concave polygonsOpenGL
community advises to make use of Triangulation libraries for breaking a polygon into triangle fans and strips. Most popular libraries fail in successfully triangulating complex polygons that are typical in GIS. In his article, we use GLU tesselator to break a polygon into convex shapes and render them.
Using the Code
I assume that the intended audience is familiar with OpenGL and understands concepts like linking with static or dynamic libraries in Microsoft Visual Studio environment.
To parse the shapefile, I have used the Shapelib (http://shapelib.maptools.org/). Include the shapefile.h at the beginning. Make use of the shapelib.lib by adding shapelib\shapelib.lib to object/library modules.
The below structure defines the structure of a polygon. Each polygon has multiple rings. Each ring has a start index and end index within the array of points.
typedef struct MyPolygonElement2D
{
vector<MyPoint2D>vPointList;
int nParts;
vector <int> vPartsStartIndex;
vector <int> vPartsEndIndex;
}MyPolygonElement2D;
We use the below function declarations to interact with GLU Tesselator
.
typedef void (__stdcall *TESSCALLBACK)(void);
void CALLBACK tess_edgeFlag( GLboolean );
void CALLBACK edgeCallback(void);
void errorCallback(GLenum errorCode);
void CALLBACK combineCallback(GLdouble coords[3],
GLdouble *vertex_data[4],GLfloat weight[4], GLdouble **dataOut );
void CALLBACK TessBeginCallback(GLenum which);
Declare a Tesselator
object and enclose the rings in contour begin and end calls to GLU tesselator
.
GLUtesselator *tobj;
gluTessBeginPolygon(tobj, NULL);
gluTessBeginContour(tobj);
gluTessEndContour(tobj);
gluTessEndPolygon(tobj);
The rendering can be accomplished with the below logic.
for(int n=0;n<vPolygonElements2D.size();n++)
{
gluTessBeginPolygon(tobj,NULL);
for(int k=0;k<vPolygonElements2D[n].nParts;k++)
{ int start=vPolygonElements2D[n].vPartsStartIndex[k];
int finish=vPolygonElements2D[n].vPartsEndIndex[k];
const int nNoOfVertices=finish-start;
double **fPolygonVertices = 0;
int nVertice=0;
fPolygonVertices = new double *[nNoOfVertices] ;
for(int i = 0 ; i < nNoOfVertices ; i++ )
fPolygonVertices[i] = new double[3];
gluTessBeginContour(tobj);
for(int l=start;l<finish;l++)
{
fPolygonVertices[nVertice][0] = vPolygonElements2D[n].vPointList[l].dX;
fPolygonVertices[nVertice][1] = vPolygonElements2D[n].vPointList[l].dY;
fPolygonVertices[nVertice][2] = 0.0;
nVertice++;
} for( l=0;l<nVertice-1 ;l++)
gluTessVertex(tobj, fPolygonVertices[l],fPolygonVertices[l]); gluTessEndContour(tobj);
} gluTessEndPolygon(tobj);
}
Output
Points of Interest
Please note the below things:
- The approach is only to demo the possibility of drawing filled polygons.
- The code is not performance oriented and written in a very legacy way.
- The shapefiles shall be WGS 84 and shall not contain multi geometry shapes.
- Performance improved by making use of display lists
- My next article would be to make use of OpenGL Shaders.
History
Refer to my previous article.