网格索引在ARX开发中的使用1.简介网格索引是GIS软件中经常使用的一种简单、高效的空间索引技术,例如著名的ESRI 公司的ArcSDE空间数据库系统就是采用了这种索引技术。
其原理非常简单,如“图1”所示,有6条直线段,被划分成了16(4x4)个网格,线段1到6分别占用了5(包括A3和B2)、2、1、3、2、2个网格。
其中B2网格同时被线段1、2、3占用,B3网格同时被线段线段1、2占用。
现在假设要对这6条线段两两求交,对于线段1来说,只有线段2、3和它有共用网格现象,所以只需和线段2、3进行计算,同理线段2只需和线段3计算即可,总共计算3次。
而如果不采用索引技术,需要计算5+4+3+2=14次。
图1而这仅仅是只有6条线段时的情况,其中的差别可想而知。
2.在ARX中使用网格索引分析网格索引技术,核心思想是把整个图形区域划分成MxN个矩形网格,每个网格都是一个数组,记录所有从中穿过的图形。
针对“图1”的情况,需要定义4X4=16个数组D[4][4],保存数据后,D[1][C]、D[1][D]中都保存了线段6,D[2][A]中保存了线段1,D[2][B]中保存了线段1、2、3,D[2][C]中保存了线段5,D[2][D]中保存了线段4,等等。
那么,在具体实现上,只需定义一个AcArray<AcDbIntArray, AcArrayObjectCopyReallocator<AcDbIntArray> >数组的数组即可,大小初始化为MXN。
下面用一个实例进行说明(假设图形都是直线段)。
2.1创建索引#define GRID_XCOUNT 100 //横向划分100个网格#define GRID_YCOUNT 100 //纵向划分100个网格void CreateSpatialIndex(const AcDbVoidPtrArray& lines,AcArray<AcDbIntArray, CArrayObjectCopyReallocator<AcDbIntArray> > &gridT oGeo, //返回空间索引AcGePoint2dArray &ptLBs, //返回每个图形的左下角AcGePoint2dArray &ptRTs, //返回每个图形的右上角AcGePoint2d &ptBase, //返回所有图形的左下角AcGeVector2d &gridSize //返回网格横、纵向尺寸){const AcDbLine* pLine;AcGePoint2d ptLB,ptRT;int i,nCount = lines.length();//计算每个图形的左下角、右上角坐标和全部图形的左下角、右上角坐标GetExtents(lines, ptLBs, ptRTs, ptBase, ptRT);//每个网格的横向和纵向尺寸(加1是处理精度问题)gridSize.x = (ptRT.x - ptBase.x)/GRID_XCOUNT + 1;gridSize.y = (ptRT.y - ptBase.y)/GRID_YCOUNT + 1;//初始化空间索引数组的内存gridToGeo.setLogicalLength(GRID_XCOUNT*GRID_YCOUNT);//创建索引int nGridXMin, nGridXMax, nGridYMin, nGridYMax;for(i=0; i<nCount; i++){//计算每个图形占用的网格(根据外包矩形简化处理)nGridXMin = (ptLBs.at(i).x - ptBase.x)/gridSize.x;nGridYMin = (ptLBs.at(i).y - ptBase.y)/gridSize.y;nGridXMax = (ptRTs.at(i).x - ptBase.x)/gridSize.x;nGridYMax = (ptRTs.at(i).y - ptBase.y)/gridSize.y;//在计算出的每个网格数组中添加当前图形的索引for(int j=nGridYMin; j<=nGridYMax; j++){for(int k=nGridXMin; k<=nGridXMax; k++){gridToGeo[j*GRID_XCOUNT+k].append(i);}}}}2.2使用索引void TestWithSpatialIndex(const AcDbVoidPtrArray& lines){AcArray<AcDbIntArray, AcArrayObjectCopyReallocator<AcDbIntArray> > gridToGeo; //索引AcGePoint2d ptBase; //图形范围左下角AcGeVector2d gridSize; //网格尺寸AcGePoint2dArray ptLBs, ptRTs; //每个图形的左下角,右上角坐标//创建空间索引CreateSpatialIndex(lines, gridToGeo, ptLBs, ptRTs, ptBase, gridSize);AcGePoint3dArray results;const AcDbLine *pLine1, *pLine2;int nGridXMin, nGridXMax, nGridYMin, nGridYMax, nIndex, nMaskBufferSize;//创建一个映射区标记计算过的图形(因为一个图形可能会被添加到多个网格中),每个图形占用1位。
nMaskBufferSize = (lines.length()+7)/8;BYTE *pMask = new BYTE[nMaskBufferSize];//两两求交for(int i=0; i<lines.length(); i++){pLine1 = (const AcDbLine*)lines.at(i);memset(pMask, 0, nMaskBufferSize); //映射区清零//计算当前图形占用的网格(根据外包矩形简化处理)nGridXMin = (ptLBs.at(i).x - ptBase.x)/gridSize.x;nGridYMin = (ptLBs.at(i).y - ptBase.y)/gridSize.y;nGridXMax = (ptRTs.at(i).x - ptBase.x)/gridSize.x;nGridYMax = (ptRTs.at(i).y - ptBase.y)/gridSize.y;//遍历占用的所有网格,和其中的图形求交for(int j=nGridYMin; j<=nGridYMax; j++){for(int k=nGridXMin; k<=nGridXMax; k++){//遍历一个网格中的所有图形for(int m=0; m<gridToGeo[j*GRID_XCOUNT+k].length(); m++){nIndex = gridToGeo[j*GRID_XCOUNT+k].at(m);if(nIndex <= i){continue; //已经计算过了}if( (pMask[nIndex/8] & (1<<(nIndex%8))) != 0 ){continue; //标记位已设,已经计算过了}//设标记位pMask[nIndex/8] |= (1<<(nIndex%8));if( (ptLBs[i].x > ptRTs[nIndex].x) ||(ptLBs[i].y > ptRTs[nIndex].y) ||(ptRTs[i].x < ptLBs[nIndex].x) ||(ptRTs[i].y < ptLBs[nIndex].y) ){continue; //两者外包矩形不相交}pLine2 = (const AcDbLine*)lines.at(nIndex);results.setLogicalLength(0);pLine1->intersectWith(pLine2, AcDb::kOnBothOperands, results);}}}}delete []pMask;}3.性能对比通过在一定范围内随机生成线段(限定长度在一定范围),对未采用空间索引和采用网格空间索引两种计算方法的性能进行对比,结果如下:图2 随机生成的图形可以发现,两者性能相差可达几十倍到100多倍,对于多于2万个图形的情况,采用网格索引后效果非常明显。
对比代码:void TestNoSpatialIndex(const AcDbVoidPtrArray& lines){int nCount = lines.length();const AcDbLine* pLine1, *pLine2;AcGePoint2dArray ptLBs, ptRTs;AcGePoint2d extentLB, extentRT;GetExtents(lines, ptLBs, ptRTs, extentLB, extentRT);AcGePoint3dArray results;//两两求交for(int i=0; i<nCount; i++){pLine1 = (const AcDbLine*)lines.at(i);for(int j=i+1; j<nCount; j++){pLine2 = (const AcDbLine*)lines.at(j);if( (ptLBs.at(i).x > ptRTs.at(j).x) ||(ptLBs.at(i).y > ptRTs.at(j).y) ||(ptRTs.at(i).x < ptLBs.at(j).x) ||(ptRTs.at(i).y < ptLBs.at(j).y) ){continue; //两者外包矩形不相交}results.setLogicalLength(0);pLine1->intersectWith(pLine2, AcDb::kOnBothOperands, results);}}}4.进一步优化从上一节中的性能对比表可以看到,图形数从1万升到10万时,采用网格索引时的耗时也升高了43倍,这是非常不利的现象。
这是因为第2节中的示例代码过于简化所致,这个问题可以通过以下几个环节进行优化处理。