shp系列(二)——利用C++进行shp文件的读(打开)
1.各数据类型及其字节数
BYTE 1; char 1; short 2; int 4; double 8;
2.位序big和little及其转换
对于位序是big的数据我们在读取时要小心。通常,数据的位序都是Little,但在有些情况下可能会是big,二者的区别在于它们位序的顺序相反。一个位序为big的数据,如果我们想得到它的真实数值,需要将它的位序转换成Little即可。转换原理就是交换字节顺序,下面是转换代码(big->little):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | int OnChangeByteOrder( int indata) { char ss[9]; char ee[8]; unsigned long val = unsigned long (indata); ultoa(val, ss, 16); // 将十六进制的数(val)转到一个字符串(ss)中,itoa(val,ss,16); int i; int length = strlen(ss); if (length != 8){ for (i = 0; i<8 - length; i++) ee[i] = '0' ; for (i = 0; i<length; i++) ee[i + 8 - length] = ss[i]; for (i = 0; i<8; i++) ss[i] = ee[i]; } //****** 进行倒序 int t; t = ss[0]; ss[0] = ss[6]; ss[6] = t; t = ss[1]; ss[1] = ss[7]; ss[7] = t; t = ss[2]; ss[2] = ss[4]; ss[4] = t; t = ss[3]; ss[3] = ss[5]; ss[5] = t; //****** 将存有十六进制数 (val) 的字符串 (ss) 中的十六进制数转成十进制数 int value = 0; for (i = 0; i<8; i++){ int k; if (ss[i] == 'a' || ss[i] == 'b' || ss[i] == 'c' || ss[i] == 'd' || ss[i] == 'e' || ss[i] == 'f' ) k = 10 + ss[i] - 'a' ; else k = ss[i] - '0' ; value = value + int (k*( int )pow((DOUBLE)16, 7 - i)); } return (value); } |
3.shp文件的基本构成
shp文件,由于包含最主要的数据坐标数据,也叫主文件、坐标文件,由文件头和实体信息构成
文件头是关于主文件的基本描述,包括版本号、文件长度等信息;实体信息是每一个图形的具体信息,也包括记录头和坐标内容。
4.shp头文件
头文件的结构如下,下面两表含义相同。
有值得说明的几点:
- 前9项都是int类型,位序是big;后8项都是double类型,位序是little,所以头文件的大小为 9 * 4 + 8 * 8 = 100。
- FileCode固定为9994,Version固定为1000。
- FileLength是指总文件长度,大小为 头文件长度 + (记录头长度 + 记录内容长度) * 记录数
- ShapeType为该shp文件图形的类型(1代表Point,3代表Polyline,5代表Polygon),更多的类型参考说明文件,目前每个 shape 文件被限定为只能包含上述类型的一种。
- Xmin,Ymin,Xmax,Ymax是空间范围的边界。
- Zmin,Zmax,Mmin,Mmxa一般用不到。
5.shp实体信息
shp实体信息分为记录头和记录内容。
5.1记录头
- RecordNumber代表当前记录的编号。每个记录的记录头都储存着记录号(Record Number)和记录内容长度(Content Length),位序都是big,类型是int,记录头的长度是 8 个字节,记录号从 1 开始。
- ContentLength代表当前记录内容的长度,不包括RecordNumber和ContentLength本身的长度,下面将以面状要素为例计算ContentLength的大小。
5.2记录内容
- ShapeType是指多边形的类型,Polygon是5。
- Box是double类型的数组,包含多边形的4个边界。
- NumParts代表Polygon的子环数,一个Polygon可能由多个环。
- Parts是一个int类型数组,数组元素个数等于NumParts,每个元素记录了每个子环的坐标在Points数组中的起始位置。
- Points记录Polygon的所有点,是Point类型,Point类型包含2个double类型(x,y均为double)。
不同的要素类型的记录内容不一样,本次以面状要素为例(Polygon,ShapeType为5),面状要素的记录内容如下:
所以一条记录(Polygon)的ContentLength(字节数)大小为:
4 + 4 * 8 + 4 + 4 + NumParts * 4 + NumPoints * 2 * 8 ,不包括RecordNumber和ContentLength本身的长度,ContentLength实际值为字节数的一半,后面FileLength也是字节数的一半。所以ContentLength = ContentLength/2。
6.shp读取的C++函数代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | void readShp( void ) { //****打开坐标文件 CFileDialog fDLG( true ); if (fDLG.DoModal() != IDOK) return ; filename = fDLG.GetPathName(); FILE* m_ShpFile_fp = fopen(filename, "rb" ); if (m_ShpFile_fp == NULL) { MessageBox( "Open File Failed" ); exit(0); } CGeoMap* map = new CGeoMap(); //创建地图对象 CGeoLayer* layer = new CGeoLayer(); //新建图层 //****定义读取坐标文件头的变量 int i; int FileCode = -1; int Unused = -1; int FileLength = -1; int Version = -1; int ShapeType = -1; double Xmin; double Ymin; double Xmax; double Ymax; double Zmin; double Zmax; double Mmin; double Mmax; //****读取坐标头文件 fread(&FileCode, sizeof ( int ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Filecode里面去,每次读一个int型字节的长度,读取一次 FileCode = OnChangeByteOrder(FileCode); //将读取的FileCode的值转化为十进制的数 for (i = 0; i<5; i++) fread(&Unused, sizeof ( int ), 1, m_ShpFile_fp); fread(&FileLength, sizeof ( int ), 1, m_ShpFile_fp); //读取FileLength FileLength = OnChangeByteOrder(FileLength); //将FileLength转化为十进制的数 fread(&Version, sizeof ( int ), 1, m_ShpFile_fp); //读取Version的值 fread(&ShapeType, sizeof ( int ), 1, m_ShpFile_fp); //读取ShapeType的值 fread(&Xmin, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Xmin里面去,每次读取一个double型字节长度,读取一次 fread(&Ymin, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Ymin里面去,每次读取一个double型字节长度,读取一次 fread(&Xmax, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Xmax里面去,每次读取一个double型字节长度,读取一次 fread(&Ymax, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Ymax里面去,每次读取一个double型字节长度,读取一次 CRect rect(Xmin, Ymin, Xmax, Ymax); layer->setRect(rect); //设置图层的边界 fread(&Zmin, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Zmin里面去,每次读取一个double型字节长度,读取一次 fread(&Zmax, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Zmax里面去,每次读取一个double型字节长度,读取一次 fread(&Mmin, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Mmin里面去,每次读取一个double型字节长度,读取一次 fread(&Mmax, sizeof ( double ), 1, m_ShpFile_fp); //从m_ShpFile_fp里面的值读到Mmax里面去,每次读取一个double型字节长度,读取一次 //****读取坐标文件头的内容结束 //****读取面状目标的实体信息 int RecordNumber; int ContentLength; switch (ShapeType) { case 5: { //polygon while ((fread(&RecordNumber, sizeof ( int ), 1, m_ShpFile_fp) != 0)) { //从第一个开始循环读取每一个Polygon fread(&ContentLength, sizeof ( int ), 1, m_ShpFile_fp); //读取ContentLength RecordNumber = OnChangeByteOrder(RecordNumber); //转换为10进制 ContentLength = OnChangeByteOrder(ContentLength); //****记录头读取结束 //****读取记录内容 int shapeType; //当前Polygon几何类型 double Box[4]; //当前Polygon的上下左右边界 int NumParts; //当前Polygon所包含的子环的个数 int NumPoints; //当前Polygon所包含的点的个数 int *Parts; //当前Polygon所包含的子环的起点在NumPoints的编号 fread(&shapeType, sizeof ( int ), 1, m_ShpFile_fp); for (i = 0; i < 4; i++) //读Box fread(Box + i, sizeof ( double ), 1, m_ShpFile_fp); fread(&NumParts, sizeof ( int ), 1, m_ShpFile_fp); //表示构成当前Polygon的子环的个数 fread(&NumPoints, sizeof ( int ), 1, m_ShpFile_fp); //表示构成当前Polygon所包含的坐标点个数 Parts = new int [NumParts]; //记录了每个子环的坐标在Points数组中的起始位置 for (i = 0; i < NumParts; i++) fread(Parts + i, sizeof ( int ), 1, m_ShpFile_fp); int pointNum; CGeoPolygon* polygon = new CGeoPolygon(); polygon->circleNum = NumParts; //设定多边形的点数 //****读取面中子环 for (i = 0; i < NumParts; i++) { if (i != NumParts - 1) pointNum = Parts[i + 1] - Parts[i]; //每个子环的长度 ,非最后一个环 else pointNum = NumPoints - Parts[i]; //最后一个环 double * PointsX = new double [pointNum]; //用于存放读取的点坐标x值; double * PointsY = new double [pointNum]; //用于存放y值 CGeoPolyline* polyline = new CGeoPolyline(); //每个环实际上就是首尾坐标相同的Polyline polyline->circleID = i; for ( int j = 0; j < pointNum; j++) { fread(PointsX + j, sizeof ( double ), 1, m_ShpFile_fp); fread(PointsY + j, sizeof ( double ), 1, m_ShpFile_fp); double a = PointsX[j]; double b = PointsY[j]; CPoint* point = new CPoint(a, b); polyline->AddPoint(point); //把该子环所有的点添加到一条链上 } CPoint pt1 = polyline->pts[0]->GetCPoint(); CPoint pt2 = polyline->pts[polyline->pts.size() - 1]->GetCPoint(); if (pt1 != pt2) { //若首位点不一致 CString str; str.Format( "%d数据首尾点不一致" , RecordNumber); polyline->pts.push_back(p1); } polygon->AddCircle(polyline); //将polyline链添加到对应polygon中 delete[] PointsX; delete[] PointsY; } //****一个面的某个子环循环结束,同时该子环已加入到polygon layer->AddObject((CGeoObject*)polygon); //将该polygon加入到图层中 delete[] Parts; } map->AddLayer(layer); } break ; case 1: //point break ; case 3: //polyline break ; default : break ; } } |
下一篇博客介绍dbf文件的读取。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?