使用C# WinForm字节流读取.shp文件以及.dbf文件并显示

您所在的位置:网站首页 如何用excel打开dbf文件 使用C# WinForm字节流读取.shp文件以及.dbf文件并显示

使用C# WinForm字节流读取.shp文件以及.dbf文件并显示

2023-07-04 07:27| 来源: 网络整理| 查看: 265

新手第一次写博客,写的不好见谅

这里给出工程完成情况 工作进度: 1.使用C#Winfrom完成了SHP文件的读取并且绘制成地图 2.同时读取相应的DBF文件,以表格的方式展现在DataGridView控件上 3.在绘制出的地图上相应位置标出了OBJECTID 4.完成了放大和缩小的功能。

存在问题: 1.进度中的3中完成的地图标记因为阀门.shp和中压天然气管.shp文件读取的记录比较多导致标记非常多,暂时还没有完成分块显示,只有营业所范围.shp比较理想 2.每次缩放都会重新绘制一次地图,比较费时间 3.没有读取SHX文件 4.在缩放的时候标记(进度中的3)不会自动更新,必须点一次刷新按钮 5.必须逐个打开SHP文件,暂未实现多个文件同时打开的功能

首先要明白文件格式,找了很多这俩个讲的比较清楚,尤其是这个.dbf文件格式普遍讲的都不是很清楚,这位大佬讲的比较好 .shp文件格式讲解 .dbf文件格式讲解

我是在这篇文章的基础上做的修改和增加功能

这里先展示效果图 在这里插入图片描述在这里插入图片描述 在这里插入图片描述 先添加几个控件 — —SplitContainer控件x2(左右分割,左边再加一个分割上下) — —panel容器x2(用作画板,这里之所以添加两个,是用来实现缩放功能的,不知道怎么做只能曲线救国了,在前面的panel1上画,panel2在底下,注意一定要把panel2的AutoScroll和AutoSize属性设置为true) — —Button控件x5 — —Label控件x1(显示缩放倍率) — —DataGridView控件x1(显示表格)

下面就要开始写代码了 先把点线面写成类方便管理

class Point { public double X; public double Y; } class Polyline { public double[] Box; public int NumParts; public int NumPoints; public List Parts; public List Points; public Polyline() { Box = new double[4]; Parts = new List(); Points = new List(); } } class Polygon:Polyline {//这里不需要添加新的变量,只需要继承线类就行了 } class Table//这个是为了读取.dbf文件并显示为表格而定义的 { public DataTable dt; public List columnsLength; public List columnsName; public List ID; public int rowCount, columnsCount; public Table() { dt = new DataTable(); columnsLength = new List(); columnsName = new List(); ID = new List(); } }

放心只有这么一个长段的代码

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Collections; using System.IO; using System.Threading; namespace ReadShapeFile_test { public partial class ReadSHPFileForm : Form {//每个.shp文件都只有一个类型 List polygons;//如果读取文件类型是Polygon则数据储存在这里 List polylines;//Polyline类型 List points;//Point类型 Pen bPen, rPen, blPen;//绘图用的Pen SolidBrush solidBrush;//填充面用的刷子 Table table;//实例table类 OpenFileDialog openFileDialog1, openFileDialog2;//打开文件窗口 int ShapeType;//记录当前.shp文件的类型 double xmin, ymin, xmax, ymax;//xy方向上的最大最小值 double n1, n2, n;//n1和n2是根据屏幕的缩放比例,n是使用放大缩小功能时的 double originWidth;//记录原始屏幕宽度 private void ReadSHPFileForm_Load(object sender, EventArgs e) {//初始化 polygons = new List(); polylines = new List(); points = new List(); bPen = new Pen(Color.Black, 1); rPen = new Pen(Color.Red, 1); blPen = new Pen(Color.Blue, 1); solidBrush = new SolidBrush(Color.Yellow); table = new Table(); originWidth = this.DrawingBoard.Width; xmin = ymin = xmax = ymax = 0; n = 1; } private void OpenSHPFileButton_Click(object sender, EventArgs e) { openFileDialog1 = new OpenFileDialog(); openFileDialog1.Filter = "shapefiles(*.shp)|*.shp|All files(*.*)|*.*";//筛选出我们所需要的文件类型 openFileDialog1.FilterIndex = 1;//选择上面第一个类型为默认类型 if (openFileDialog1.ShowDialog() == DialogResult.OK)//窗口点击确认则为true {//打开的文件是根据openFileDialog1.FileName这个属性记录的文件路径所确认的 ReadSHPFile(openFileDialog1);//调用下面自定义的读取.shp文件函数 char[] path = openFileDialog1.FileName.ToCharArray();//记录文件路径 if (path.Length != 0)//下面这一套操作是为了实现在读取.shp文件的时候同时读取同名的.dbf文件 {//因为是同名又在一个路径下,所以只需要更改文件类型就可以了 path[path.Length - 1] = 'f'; path[path.Length - 2] = 'b'; path[path.Length - 3] = 'd'; openFileDialog1.FileName = new string(path);//把新的路径重新赋值给openFileDialog1.FileName ReadDBFFile(openFileDialog1);//用新的路径调用下面自定义的读取.dbf文件函数 }//大佬更好的同时读取的方法欢迎分享,我找了好久没找到,这个笨方法是自己想的 DrawingBoard.Refresh();//刷新panel1(这里改了个名字) } } private void OpenDBFFileButton_Click(object sender, EventArgs e) {//这里是单独打开.dbf文件的按钮,前期没有完成上面同时读取的功能所写的,现在没什么用了但是还是保留了 openFileDialog2 = new OpenFileDialog(); openFileDialog2.Filter = "dbffiles(*.dbf)|*.dbf|All files(*.*)|*.*"; openFileDialog2.FilterIndex = 1; if (openFileDialog2.ShowDialog() == DialogResult.OK) { ReadDBFFile(openFileDialog2); } } private void RefreshButton_Click(object sender, EventArgs e) { DrawingBoard.Controls.Clear();//这里是清理在地图上标记.dbf文件所记录的属性用的,可以先看后面 DrawingBoard.Refresh();//依然是刷新 } private void MagnifyButton_Click(object sender, EventArgs e) {//放大按钮 if (openFileDialog1 != null) { n += 0.1;//放大倍率加0.1 double width = xmax - xmin;//算出原始地图的宽度 double height = ymax - ymin;//高度 int w = (int)(originWidth * n);//缩放之后应显示的宽度 int h = (int)(originWidth / width * height * n);//这里是按照宽高比例算的缩放后的高度,这也就是只记录初始屏幕宽度而不记录高度的原因,缩放地图要按照地图原始的宽高比缩放 DrawingBoard.Size = new Size(w, h);//改变panel1的大小 DrawingBoard.Refresh(); } } private void ShrinkButton_Click(object sender, EventArgs e) {//缩小按钮同理 if (openFileDialog1 != null) { n -= 0.1; double width = xmax - xmin; double height = ymax - ymin; int w = (int)(originWidth * n); int h = (int)(originWidth / width * height * n); DrawingBoard.Size = new Size(w, h); DrawingBoard.Refresh(); } } private void DarwingBoard_Paint(object sender, PaintEventArgs e) { double width = xmax - xmin; double height = ymax - ymin; n1 = (float)(originWidth * 0.9 / width);//算出地图适应屏幕而要本身要缩放的比例(不是用户操作的缩放比例),0.9是为了留出百边 n2 = (float)(n1 / width * height); n1 *= n;//这里才是乘上用户需要的缩放比例 n2 *= n; ZoomLabel.Text = "X" + n.ToString();//把这个比例显示在Label中 if (polygons != null) DrawPolygons(e);//画面 if (polylines != null) DrawPolylines(e);//画线 if (points != null) DrawPoints(e);//画点 } private void ReadDBFFile(OpenFileDialog openFileDialog) { table.dt.Columns.Clear();//一系列的清空 table.dt.Rows.Clear(); table.columnsName.Clear(); table.columnsLength.Clear(); BinaryReader br = new BinaryReader(openFileDialog.OpenFile()); _ = br.ReadByte();//当前版本信息 _ = br.ReadBytes(3);//最近更新日期 table.rowCount = br.ReadInt32();//文件中的记录条数 table.columnsCount = (br.ReadInt16()-33)/32;//根据文件头中的字节数计算一下有多少列或者说多少个记录项(怎么算就去看上面的.dbf格式吧) _ = br.ReadInt16();//一条记录中的字节长度 _ = br.ReadBytes(20);//系统保留 for(int i=0; i _ = br.ReadByte();//占位符 DataRow dr;//一行 dr = table.dt.NewRow(); for (int j = 0; j //读取主文件头 BinaryReader br = new BinaryReader(openFileDialog.OpenFile()); _ = br.ReadBytes(24);//文件编号及未被使用的字段 _ = br.ReadInt32();//文件长度 _ = br.ReadInt32();//版本 ShapeType = br.ReadInt32();//储存类型编号 double temp = br.ReadDouble();//下面是储存地图的xy最大最小 if (xmin == 0 || temp ymax)//所以第二个数据本应该是y最小,这里加个负号就是y最大了 ymax = temp;//画个坐标轴吧,讲起来比较麻烦,只是所有的y值加了负号变换到第二坐标系了 temp = br.ReadDouble(); if (xmax == 0 || temp > xmax) xmax = temp; temp = -br.ReadDouble(); if (ymin == 0 || temp Point point = new Point(); _ = br.ReadInt32();//记录编号 _ = br.ReadInt32();//记录内容长度 _ = br.ReadInt32();//记录内容头的图形类型编号 point.X = br.ReadDouble(); point.Y = -br.ReadDouble();//y值都加个负号 points.Add(point);//储存点 } //下面是把坐标输出成txt文件,没有需要注释掉就好了 //StreamWriter sw = new StreamWriter("D://point.txt"); //foreach (Point p in points) //{ // sw.WriteLine("{0},{1},{2} ", p.X, -1 * p.Y, 0); //} //sw.Close(); break; case 3: polylines.Clear(); while (br.PeekChar() != -1) { _ = br.ReadInt32();//记录编号 _ = br.ReadInt32();//记录内容长度 _ = br.ReadInt32();//记录内容头的图形类型编号 Polyline polyline = new Polyline(); polyline.Box[0] = br.ReadDouble();//记录每条线的xy最大最小 polyline.Box[3] = -br.ReadDouble();//这里是Box[3]哦 polyline.Box[2] = br.ReadDouble();//0,1,2,3对应下面 polyline.Box[1] = -br.ReadDouble();//xmin,ymin,xmax,ymax polyline.NumParts = br.ReadInt32(); polyline.NumPoints = br.ReadInt32(); for (int i = 0; i Point point = new Point(); point.X = br.ReadDouble(); point.Y = -br.ReadDouble();//同样加负号 polyline.Points.Add(point);//储存 } polylines.Add(polyline);//储存 } break; case 5: polygons.Clear(); while (br.PeekChar() != -1) {//记录的都一样和上面的线 _ = br.ReadInt32();//记录编号 _ = br.ReadInt32();//记录内容长度 _ = br.ReadInt32();//记录内容头的图形类型编号 Polygon polygon = new Polygon(); polygon.Box[0] = br.ReadDouble(); polygon.Box[3] = -br.ReadDouble(); polygon.Box[2] = br.ReadDouble(); polygon.Box[1] = -br.ReadDouble(); polygon.NumParts = br.ReadInt32(); polygon.NumPoints = br.ReadInt32(); for (int i = 0; i Point point = new Point(); point.X = br.ReadDouble(); point.Y = -br.ReadDouble(); polygon.Points.Add(point); } polygons.Add(polygon); } break; } } private void DrawPolygons(PaintEventArgs e) { PointF[] point; int count = 0;//为了标记的计数 .shp和.dbf的记录是一一对应的 foreach (Polygon p in polygons) { for (int i = 0; i //先看else吧 startpoint = p.Parts[i]; endpoint = p.NumPoints; } else { startpoint = p.Parts[i]; endpoint = p.Parts[i + 1];//这里是下一个部分的第一个点的索引,因为是连续的所以这里的索引减一就是上一个部分的最后一个点的索引值 } point = new PointF[endpoint - startpoint];//一共就这么多的点 for (int k = 0, j = startpoint; j int xx = (int)(10 + (p.Box[0] + p.Box[2] - 2 * xmin) * n1) / 2;//算出中心点 int yy = (int)(10 + (p.Box[1] + p.Box[3] - 2 * ymin) * n2) / 2; Label label = new Label() {//新生成一个label去标记 AutoSize = false, Size = new Size(50, 12), BackColor = Color.Yellow, BorderStyle = BorderStyle.Fixed3D, Text = "ID:" + Convert.ToInt32(table.ID[count]),//顺序一一对应的,一个一个往下就可以了,不会错的 Location = new System.Drawing.Point(xx, yy) }; DrawingBoard.Controls.Add(label);//显示到屏幕上,上面的刷新按钮中的DrawingBoard.Controls.Clear();就是用来清空它的 ++count;//下一个 } } } private void DrawPolylines(PaintEventArgs e) {//和上面是一样的 PointF[] point; //int count = 0; foreach (Polyline p in polylines) { for (int i = 0; i startpoint = p.Parts[i]; endpoint = p.NumPoints; } else { startpoint = p.Parts[i]; endpoint = p.Parts[i + 1]; } point = new PointF[endpoint - startpoint]; for (int k = 0, j = startpoint; j // int xx = (int)(10 + (p.Box[0] + p.Box[2] - 2 * xmin) * n1) / 2; // int yy = (int)(10 + (p.Box[1] + p.Box[3] - 2 * ymin) * n2) / 2; // Label label = new Label() // { // AutoSize = false, // Size = new Size(50, 12), // BackColor = Color.Yellow, // BorderStyle = BorderStyle.Fixed3D, // Text = "ID:" + Convert.ToInt32(table.ID[count]), // Location = new System.Drawing.Point(xx, yy) // }; // DrawingBoard.Controls.Add(label); // ++count; //} } } private void DrawPoints(PaintEventArgs e) {//画点就比较简单了 foreach (Point p in points) { PointF pp = new PointF(); pp.X = (float)(10 + (p.X - xmin) * n1); pp.Y = (float)(10 + (p.Y - ymin) * n2); e.Graphics.DrawEllipse(blPen, pp.X, pp.Y, 1f, 1f); } } public ReadSHPFileForm() { InitializeComponent(); } } }

自己写的时候没有找的合适的教程,自己摸索着一点一点的写完,把我的代码记录下来希望对后面的人有帮助,讲的不是很明白的地方多看多想,我相信你能明白的~~~~~~~~~哈哈哈哈哈。

最后还是谢谢marine的博客给了我很多的思路。(我是在此基础上改了好多又加了好多,如果觉得我的比较乱,先看看这个吧,虽然只有.shp文件的读取但思路是一样的)

前人栽树后人乘凉,乘凉之后也要记得栽树哦。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3