C# 读取和绘制 Shapefile (SHP) 文件

用于读取 ESRI Shapefile (SHP) 文件并在 Windows Forms 窗口中绘制地理要素。该实现使用 DotSpatial 库处理 Shapefile 文件,支持点、线、面等几何类型的绘制。

解决方案结构

复制代码
ShapefileViewer/
├── ShapefileViewer.sln
├── ShapefileViewer/
│   ├── App.config
│   ├── Form1.cs
│   ├── Form1.Designer.cs
│   ├── Form1.resx
│   ├── Program.cs
│   ├── ShpReader.cs
│   ├── MapRenderer.cs
│   └── Properties/
│       ├── AssemblyInfo.cs
│       └── Resources.Designer.cs
└── packages.config

完整代码实现

1. 主窗体 (Form1.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
using DotSpatial.Data;
using DotSpatial.Symbology;
using DotSpatial.Topology;

namespace ShapefileViewer
{
    public partial class Form1 : Form
    {
        private ShpReader shpReader;
        private MapRenderer mapRenderer;
        private FeatureSet currentLayer;
        private ScaleBar scaleBar;
        private NorthArrow northArrow;
        private bool showScaleBar = true;
        private bool showNorthArrow = true;
        private bool showGrid = true;
        private float zoomFactor = 1.0f;
        private PointF panOffset = new PointF(0, 0);
        private Point lastMousePosition;
        private bool isPanning = false;

        public Form1()
        {
            InitializeComponent();
            InitializeMapComponents();
            InitializeUI();
        }

        private void InitializeMapComponents()
        {
            shpReader = new ShpReader();
            mapRenderer = new MapRenderer();
            scaleBar = new ScaleBar();
            northArrow = new NorthArrow();
        }

        private void InitializeUI()
        {
            // 窗体设置
            this.Text = "Shapefile 查看器";
            this.Size = new Size(1200, 800);
            this.BackColor = Color.LightGray;
            this.DoubleBuffered = true;

            // 创建地图控件
            mapPanel = new Panel
            {
                Location = new Point(10, 10),
                Size = new Size(880, 700),
                BorderStyle = BorderStyle.FixedSingle,
                BackColor = Color.White,
                Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right
            };
            mapPanel.Paint += MapPanel_Paint;
            mapPanel.MouseWheel += MapPanel_MouseWheel;
            mapPanel.MouseDown += MapPanel_MouseDown;
            mapPanel.MouseMove += MapPanel_MouseMove;
            mapPanel.MouseUp += MapPanel_MouseUp;

            // 创建控制面板
            controlPanel = new Panel
            {
                Location = new Point(900, 10),
                Size = new Size(280, 700),
                BorderStyle = BorderStyle.FixedSingle,
                BackColor = Color.FromArgb(240, 240, 240),
                Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right
            };

            // 添加控件
            int yPos = 20;
            int labelWidth = 80;
            int controlWidth = 170;
            int rowHeight = 30;

            // 打开文件按钮
            btnOpen = new Button
            {
                Text = "打开 Shapefile",
                Location = new Point(20, yPos),
                Size = new Size(controlWidth, 30),
                BackColor = Color.SteelBlue,
                ForeColor = Color.White,
                FlatStyle = FlatStyle.Flat
            };
            btnOpen.FlatAppearance.BorderSize = 0;
            btnOpen.Click += BtnOpen_Click;
            controlPanel.Controls.Add(btnOpen);

            // 缩放控制
            yPos += rowHeight + 10;
            lblZoom = new Label { Text = "缩放:", Location = new Point(20, yPos), Size = new Size(labelWidth, 20) };
            controlPanel.Controls.Add(lblZoom);

            tbZoom = new TrackBar
            {
                Minimum = 10,
                Maximum = 500,
                Value = 100,
                TickFrequency = 10,
                Location = new Point(20, yPos + 25),
                Size = new Size(controlWidth, 45)
            };
            tbZoom.ValueChanged += TbZoom_ValueChanged;
            controlPanel.Controls.Add(tbZoom);

            lblZoomValue = new Label { Text = "100%", Location = new Point(controlWidth + 30, yPos + 40), Size = new Size(50, 20) };
            controlPanel.Controls.Add(lblZoomValue);

            // 平移控制
            yPos += rowHeight + 60;
            btnPan = new Button
            {
                Text = "平移模式",
                Location = new Point(20, yPos),
                Size = new Size(controlWidth, 30),
                BackColor = Color.ForestGreen,
                ForeColor = Color.White,
                FlatStyle = FlatStyle.Flat
            };
            btnPan.FlatAppearance.BorderSize = 0;
            btnPan.Click += BtnPan_Click;
            controlPanel.Controls.Add(btnPan);

            btnResetView = new Button
            {
                Text = "重置视图",
                Location = new Point(20, yPos + 40),
                Size = new Size(controlWidth, 30),
                BackColor = Color.Orange,
                ForeColor = Color.White,
                FlatStyle = FlatStyle.Flat
            };
            btnResetView.FlatAppearance.BorderSize = 0;
            btnResetView.Click += BtnResetView_Click;
            controlPanel.Controls.Add(btnResetView);

            // 图层控制
            yPos += rowHeight + 90;
            chkShowScaleBar = new CheckBox { Text = "显示比例尺", Location = new Point(20, yPos), Checked = true, AutoSize = true };
            chkShowScaleBar.CheckedChanged += ChkShowScaleBar_CheckedChanged;
            controlPanel.Controls.Add(chkShowScaleBar);

            chkShowNorthArrow = new CheckBox { Text = "显示指北针", Location = new Point(20, yPos + 30), Checked = true, AutoSize = true };
            chkShowNorthArrow.CheckedChanged += ChkShowNorthArrow_CheckedChanged;
            controlPanel.Controls.Add(chkShowNorthArrow);

            chkShowGrid = new CheckBox { Text = "显示网格", Location = new Point(20, yPos + 60), Checked = true, AutoSize = true };
            chkShowGrid.CheckedChanged += ChkShowGrid_CheckedChanged;
            controlPanel.Controls.Add(chkShowGrid);

            // 图层列表
            yPos += rowHeight + 100;
            lblLayers = new Label { Text = "图层:", Location = new Point(20, yPos), AutoSize = true };
            controlPanel.Controls.Add(lblLayers);

            lstLayers = new ListBox
            {
                Location = new Point(20, yPos + 25),
                Size = new Size(controlWidth, 150),
                SelectionMode = SelectionMode.One
            };
            lstLayers.SelectedIndexChanged += LstLayers_SelectedIndexChanged;
            controlPanel.Controls.Add(lstLayers);

            // 状态标签
            statusLabel = new Label
            {
                Text = "就绪",
                Location = new Point(10, 720),
                Size = new Size(1160, 20),
                Anchor = AnchorStyles.Bottom | AnchorStyles.Left
            };
            this.Controls.Add(statusLabel);

            // 添加控件到窗体
            this.Controls.Add(mapPanel);
            this.Controls.Add(controlPanel);
        }

        #region 控件声明
        private Panel mapPanel;
        private Panel controlPanel;
        private Button btnOpen;
        private Button btnPan;
        private Button btnResetView;
        private TrackBar tbZoom;
        private Label lblZoom;
        private Label lblZoomValue;
        private CheckBox chkShowScaleBar;
        private CheckBox chkShowNorthArrow;
        private CheckBox chkShowGrid;
        private Label lblLayers;
        private ListBox lstLayers;
        private Label statusLabel;
        #endregion

        #region 事件处理
        private void BtnOpen_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog openFileDialog = new OpenFileDialog())
            {
                openFileDialog.Filter = "Shapefile 文件 (*.shp)|*.shp|所有文件 (*.*)|*.*";
                openFileDialog.Title = "选择 Shapefile 文件";

                if (openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        statusLabel.Text = $"正在加载: {Path.GetFileName(openFileDialog.FileName)}...";
                        Application.DoEvents();

                        // 加载 shapefile
                        currentLayer = shpReader.LoadShapefile(openFileDialog.FileName);
                        if (currentLayer != null)
                        {
                            // 添加到图层列表
                            lstLayers.Items.Add(Path.GetFileNameWithoutExtension(openFileDialog.FileName));
                            lstLayers.SelectedIndex = lstLayers.Items.Count - 1;

                            // 重置视图
                            zoomFactor = 1.0f;
                            panOffset = new PointF(0, 0);
                            tbZoom.Value = 100;
                            lblZoomValue.Text = "100%";

                            statusLabel.Text = $"已加载: {Path.GetFileName(openFileDialog.FileName)} | 要素数: {currentLayer.Features.Count}";
                        }
                        else
                        {
                            statusLabel.Text = "加载失败: 无效的文件格式";
                        }
                    }
                    catch (Exception ex)
                    {
                        statusLabel.Text = $"错误: {ex.Message}";
                        MessageBox.Show($"加载 Shapefile 失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    finally
                    {
                        mapPanel.Invalidate();
                    }
                }
            }
        }

        private void MapPanel_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.Clear(Color.White);

            if (currentLayer == null) return;

            // 应用变换
            g.TranslateTransform(panOffset.X, panOffset.Y);
            g.ScaleTransform(zoomFactor, zoomFactor);

            // 绘制地图内容
            mapRenderer.Render(g, currentLayer, mapPanel.ClientSize);

            // 绘制网格
            if (showGrid)
            {
                mapRenderer.DrawGrid(g, mapPanel.ClientSize, zoomFactor);
            }

            // 绘制指北针
            if (showNorthArrow)
            {
                northArrow.Draw(g, new PointF(mapPanel.ClientSize.Width - 50, 50), 30);
            }

            // 绘制比例尺
            if (showScaleBar)
            {
                scaleBar.Draw(g, new PointF(20, mapPanel.ClientSize.Height - 40), zoomFactor);
            }
        }

        private void TbZoom_ValueChanged(object sender, EventArgs e)
        {
            zoomFactor = tbZoom.Value / 100.0f;
            lblZoomValue.Text = $"{tbZoom.Value}%";
            mapPanel.Invalidate();
        }

        private void BtnPan_Click(object sender, EventArgs e)
        {
            isPanning = !isPanning;
            btnPan.BackColor = isPanning ? Color.OrangeRed : Color.ForestGreen;
            mapPanel.Cursor = isPanning ? Cursors.Hand : Cursors.Default;
        }

        private void BtnResetView_Click(object sender, EventArgs e)
        {
            zoomFactor = 1.0f;
            panOffset = new PointF(0, 0);
            tbZoom.Value = 100;
            lblZoomValue.Text = "100%";
            mapPanel.Invalidate();
        }

        private void ChkShowScaleBar_CheckedChanged(object sender, EventArgs e)
        {
            showScaleBar = chkShowScaleBar.Checked;
            mapPanel.Invalidate();
        }

        private void ChkShowNorthArrow_CheckedChanged(object sender, EventArgs e)
        {
            showNorthArrow = chkShowNorthArrow.Checked;
            mapPanel.Invalidate();
        }

        private void ChkShowGrid_CheckedChanged(object sender, EventArgs e)
        {
            showGrid = chkShowGrid.Checked;
            mapPanel.Invalidate();
        }

        private void LstLayers_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstLayers.SelectedIndex >= 0)
            {
                // 在实际应用中,这里应该加载选中的图层
                // 本示例中只显示当前加载的图层
                mapPanel.Invalidate();
            }
        }

        private void MapPanel_MouseWheel(object sender, MouseEventArgs e)
        {
            // 缩放功能
            int zoomDelta = e.Delta > 0 ? 10 : -10;
            tbZoom.Value = Math.Min(Math.Max(tbZoom.Minimum, tbZoom.Value + zoomDelta), tbZoom.Maximum);
        }

        private void MapPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (isPanning && e.Button == MouseButtons.Left)
            {
                lastMousePosition = e.Location;
                mapPanel.Capture = true;
            }
        }

        private void MapPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (isPanning && e.Button == MouseButtons.Left)
            {
                panOffset.X += e.X - lastMousePosition.X;
                panOffset.Y += e.Y - lastMousePosition.Y;
                lastMousePosition = e.Location;
                mapPanel.Invalidate();
            }
        }

        private void MapPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (isPanning && e.Button == MouseButtons.Left)
            {
                mapPanel.Capture = false;
            }
        }
        #endregion
    }

    #region 辅助类
    public class ShpReader
    {
        public FeatureSet LoadShapefile(string filePath)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException("Shapefile 文件不存在", filePath);

            // 检查是否存在同名的 .shx 文件
            string shxPath = Path.ChangeExtension(filePath, ".shx");
            string dbfPath = Path.ChangeExtension(filePath, ".dbf");

            if (!File.Exists(shxPath))
                throw new FileNotFoundException("找不到关联的 .shx 文件", shxPath);

            if (!File.Exists(dbfPath))
                throw new FileNotFoundException("找不到关联的 .dbf 文件", dbfPath);

            // 使用 DotSpatial 加载 Shapefile
            return FeatureSet.Open(filePath);
        }
    }

    public class MapRenderer
    {
        private readonly Font labelFont = new Font("Arial", 8);
        private readonly Brush labelBrush = Brushes.Black;
        private readonly Pen gridPen = new Pen(Color.LightGray, 1);

        public void Render(Graphics g, FeatureSet featureSet, Size panelSize)
        {
            if (featureSet == null || featureSet.Features.Count == 0)
                return;

            // 计算边界框
            Extent extent = featureSet.Extent;
            double width = extent.Width;
            double height = extent.Height;

            // 计算缩放比例
            float scaleX = (float)(panelSize.Width / width);
            float scaleY = (float)(panelSize.Height / height);
            float scale = Math.Min(scaleX, scaleY) * 0.9f; // 留出一些边距

            // 计算偏移量以居中显示
            float offsetX = (float)(-extent.MinX * scale) + (panelSize.Width - (float)width * scale) / 2;
            float offsetY = (float)(extent.MaxY * scale) + (panelSize.Height - (float)height * scale) / 2;

            // 绘制每个要素
            foreach (IFeature feature in featureSet.Features)
            {
                IGeometry geometry = feature.BasicGeometry;
                DrawGeometry(g, geometry, scale, offsetX, offsetY);
            }
        }

        private void DrawGeometry(Graphics g, IGeometry geometry, float scale, float offsetX, float offsetY)
        {
            if (geometry is Point point)
            {
                DrawPoint(g, point, scale, offsetX, offsetY);
            }
            else if (geometry is LineString lineString)
            {
                DrawLineString(g, lineString, scale, offsetX, offsetY);
            }
            else if (geometry is Polygon polygon)
            {
                DrawPolygon(g, polygon, scale, offsetX, offsetY);
            }
            else if (geometry is MultiPoint multiPoint)
            {
                foreach (Point pt in multiPoint.Geometries)
                {
                    DrawPoint(g, pt, scale, offsetX, offsetY);
                }
            }
            else if (geometry is MultiLineString multiLineString)
            {
                foreach (LineString ls in multiLineString.Geometries)
                {
                    DrawLineString(g, ls, scale, offsetX, offsetY);
                }
            }
            else if (geometry is MultiPolygon multiPolygon)
            {
                foreach (Polygon poly in multiPolygon.Geometries)
                {
                    DrawPolygon(g, poly, scale, offsetX, offsetY);
                }
            }
        }

        private void DrawPoint(Graphics g, Point point, float scale, float offsetX, float offsetY)
        {
            float x = (float)(point.X * scale + offsetX);
            float y = (float)(-point.Y * scale + offsetY); // Y坐标反转

            // 绘制点符号
            g.FillEllipse(Brushes.Red, x - 3, y - 3, 6, 6);
            g.DrawEllipse(Pens.Black, x - 3, y - 3, 6, 6);
        }

        private void DrawLineString(Graphics g, LineString lineString, float scale, float offsetX, float offsetY)
        {
            PointF[] points = new PointF[lineString.Coordinates.Count];
            for (int i = 0; i < lineString.Coordinates.Count; i++)
            {
                Coordinate coord = lineString.Coordinates[i];
                float x = (float)(coord.X * scale + offsetX);
                float y = (float)(-coord.Y * scale + offsetY); // Y坐标反转
                points[i] = new PointF(x, y);
            }

            // 绘制线
            g.DrawLines(Pens.Blue, points);
        }

        private void DrawPolygon(Graphics g, Polygon polygon, float scale, float offsetX, float offsetY)
        {
            // 绘制外环
            DrawRing(g, polygon.Shell, scale, offsetX, offsetY, Pens.Green, Brushes.LightGreen);

            // 绘制内环(孔洞)
            foreach (LinearRing hole in polygon.Holes)
            {
                DrawRing(g, hole, scale, offsetX, offsetY, Pens.Red, Brushes.LightPink);
            }
        }

        private void DrawRing(Graphics g, LinearRing ring, float scale, float offsetX, float offsetY, Pen pen, Brush brush)
        {
            PointF[] points = new PointF[ring.Coordinates.Count];
            for (int i = 0; i < ring.Coordinates.Count; i++)
            {
                Coordinate coord = ring.Coordinates[i];
                float x = (float)(coord.X * scale + offsetX);
                float y = (float)(-coord.Y * scale + offsetY); // Y坐标反转
                points[i] = new PointF(x, y);
            }

            // 填充多边形
            if (brush != null)
                g.FillPolygon(brush, points);

            // 绘制边界
            g.DrawPolygon(pen, points);
        }

        public void DrawGrid(Graphics g, Size panelSize, float zoomFactor)
        {
            float gridSize = 50 * zoomFactor;
            float offsetX = panelSize.Width % gridSize / 2;
            float offsetY = panelSize.Height % gridSize / 2;

            for (float x = offsetX; x < panelSize.Width; x += gridSize)
            {
                g.DrawLine(gridPen, x, 0, x, panelSize.Height);
            }

            for (float y = offsetY; y < panelSize.Height; y += gridSize)
            {
                g.DrawLine(gridPen, 0, y, panelSize.Width, y);
            }
        }
    }

    public class ScaleBar
    {
        public void Draw(Graphics g, PointF location, float zoomFactor)
        {
            float barLength = 100 * zoomFactor;
            float barHeight = 10;

            // 绘制比例尺条
            g.FillRectangle(Brushes.White, location.X, location.Y, barLength, barHeight);
            g.DrawRectangle(Pens.Black, location.X, location.Y, barLength, barHeight);

            // 绘制刻度
            g.DrawLine(Pens.Black, location.X, location.Y - 5, location.X, location.Y + barHeight + 5);
            g.DrawLine(Pens.Black, location.X + barLength, location.Y - 5, location.X + barLength, location.Y + barHeight + 5);

            // 绘制标签
            Font font = new Font("Arial", 8);
            string label = $"{barLength / 100 * 10} km"; // 简化计算
            g.DrawString(label, font, Brushes.Black, location.X, location.Y + barHeight + 5);
        }
    }

    public class NorthArrow
    {
        public void Draw(Graphics g, PointF location, float size)
        {
            // 绘制箭头
            PointF[] arrowPoints =
            {
                new PointF(location.X, location.Y - size / 2),
                new PointF(location.X + size / 2, location.Y + size / 2),
                new PointF(location.X - size / 2, location.Y + size / 2)
            };

            g.FillPolygon(Brushes.White, arrowPoints);
            g.DrawPolygon(Pens.Black, arrowPoints);

            // 绘制文字
            Font font = new Font("Arial", 10, FontStyle.Bold);
            g.DrawString("N", font, Brushes.Black, location.X - 5, location.Y - size / 2 - 15);
        }
    }
    #endregion
}

2. 程序入口 (Program.cs)

csharp 复制代码
using System;
using System.Windows.Forms;

namespace ShapefileViewer
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

3. 应用程序配置文件 (App.config)

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
  </startup>
</configuration>

4. NuGet 包配置 (packages.config)

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="DotSpatial.Core" version="1.9.0" targetFramework="net472" />
  <package id="DotSpatial.Data" version="1.9.0" targetFramework="net472" />
  <package id="DotSpatial.Symbology" version="1.9.0" targetFramework="net472" />
  <package id="DotSpatial.Topology" version="1.9.0" targetFramework="net472" />
  <package id="NetTopologySuite" version="1.15.3" targetFramework="net472" />
</packages>

功能特点

1. 文件读取功能

  • 支持标准 ESRI Shapefile 格式 (.shp)
  • 自动检测并加载关联的 .shx 和 .dbf 文件
  • 支持点、线、面等几何类型
  • 支持多点、多线、多面体等复合类型

2. 地图显示功能

  • 自动缩放以适应窗口大小
  • 平滑的缩放和平移操作
  • 支持鼠标滚轮缩放
  • 支持拖拽平移
  • 网格显示(可开关)

3. 地图元素

  • 比例尺(可开关)
  • 指北针(可开关)
  • 图层管理
  • 状态显示

4. 用户界面

  • 直观的控制面板
  • 缩放滑块控制
  • 平移模式切换
  • 视图重置按钮
  • 图层列表
  • 状态栏

使用说明

1. 环境要求

  • .NET Framework 4.7.2 或更高版本

  • 安装 NuGet 包:

    复制代码
    Install-Package DotSpatial.Core
    Install-Package DotSpatial.Data
    Install-Package DotSpatial.Symbology
    Install-Package DotSpatial.Topology
    Install-Package NetTopologySuite

2. 操作步骤

  1. 运行程序
  2. 点击"打开 Shapefile"按钮
  3. 选择要打开的 .shp 文件
  4. 使用鼠标滚轮缩放地图
  5. 点击"平移模式"后拖拽地图
  6. 使用控制面板调整显示选项

3. 支持的 Shapefile 类型

  • 点 (Point)
  • 线 (Polyline)
  • 面 (Polygon)
  • 多点 (MultiPoint)
  • 多线 (MultiPolyline)
  • 多面体 (MultiPolygon)

参考代码 学会用C#文件读取的shp(shapefile格式)文件并在窗口绘制 www.youwenfan.com/contentcst/39627.html

技术实现细节

1. Shapefile 读取

csharp 复制代码
public FeatureSet LoadShapefile(string filePath)
{
    // 检查文件存在性
    if (!File.Exists(filePath)) throw new FileNotFoundException(...);
    
    // 检查关联文件
    string shxPath = Path.ChangeExtension(filePath, ".shx");
    string dbfPath = Path.ChangeExtension(filePath, ".dbf");
    
    if (!File.Exists(shxPath) || !File.Exists(dbfPath)) 
        throw new FileNotFoundException(...);
    
    // 使用 DotSpatial 加载
    return FeatureSet.Open(filePath);
}

2. 坐标变换

csharp 复制代码
// 计算缩放比例
float scaleX = (float)(panelSize.Width / width);
float scaleY = (float)(panelSize.Height / height);
float scale = Math.Min(scaleX, scaleY) * 0.9f;

// 计算偏移量
float offsetX = (float)(-extent.MinX * scale) + (panelSize.Width - (float)width * scale) / 2;
float offsetY = (float)(extent.MaxY * scale) + (panelSize.Height - (float)height * scale) / 2;

// 应用变换
float x = (float)(coord.X * scale + offsetX);
float y = (float)(-coord.Y * scale + offsetY); // Y坐标反转

3. 几何绘制

csharp 复制代码
// 点绘制
g.FillEllipse(Brushes.Red, x - 3, y - 3, 6, 6);
g.DrawEllipse(Pens.Black, x - 3, y - 3, 6, 6);

// 线绘制
g.DrawLines(Pens.Blue, points);

// 面绘制
g.FillPolygon(brush, points);
g.DrawPolygon(pen, points);

扩展功能建议

1. 添加属性查询功能

csharp 复制代码
// 在 Form1 类中添加
private void LstLayers_MouseDoubleClick(object sender, MouseEventArgs e)
{
    if (lstLayers.SelectedIndex >= 0 && currentLayer != null)
    {
        // 显示属性表
        ShowAttributeTable(currentLayer);
    }
}

private void ShowAttributeTable(FeatureSet layer)
{
    Form attrForm = new Form();
    attrForm.Text = "属性表 - " + layer.Name;
    attrForm.Size = new Size(800, 600);
    
    DataGridView grid = new DataGridView();
    grid.Dock = DockStyle.Fill;
    grid.ReadOnly = true;
    grid.DataSource = layer.DataTable;
    
    attrForm.Controls.Add(grid);
    attrForm.ShowDialog();
}

2. 添加空间查询功能

csharp 复制代码
// 在 MapRenderer 类中添加
public IFeature SelectFeature(PointF clickPoint, FeatureSet layer, float scale, float offsetX, float offsetY)
{
    // 将屏幕坐标转换为地图坐标
    double mapX = (clickPoint.X - offsetX) / scale;
    double mapY = -(clickPoint.Y - offsetY) / scale;
    
    // 查找最近的要素
    IFeature closestFeature = null;
    double minDistance = double.MaxValue;
    
    foreach (IFeature feature in layer.Features)
    {
        double distance = feature.Geometry.Distance(new Coordinate(mapX, mapY));
        if (distance < minDistance)
        {
            minDistance = distance;
            closestFeature = feature;
        }
    }
    
    // 检查是否在容差范围内
    if (minDistance < 10.0 / scale) // 10像素容差
        return closestFeature;
    
    return null;
}

3. 添加图层样式编辑

csharp 复制代码
// 在 Form1 类中添加
private void LstLayers_MouseClick(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Right && lstLayers.SelectedIndex >= 0)
    {
        ContextMenuStrip menu = new ContextMenuStrip();
        
        ToolStripMenuItem colorItem = new ToolStripMenuItem("更改颜色");
        colorItem.Click += (s, args) => ChangeLayerColor();
        menu.Items.Add(colorItem);
        
        ToolStripMenuItem widthItem = new ToolStripMenuItem("更改线宽");
        widthItem.Click += (s, args) => ChangeLineWidth();
        menu.Items.Add(widthItem);
        
        menu.Show(lstLayers, e.Location);
    }
}

private void ChangeLayerColor()
{
    if (currentLayer != null)
    {
        ColorDialog dialog = new ColorDialog();
        if (dialog.ShowDialog() == DialogResult.OK)
        {
            // 在实际应用中,更新图层样式
            mapPanel.Invalidate();
        }
    }
}

4. 添加导出图片功能

csharp 复制代码
// 在 Form1 类中添加
private void BtnExport_Click(object sender, EventArgs e)
{
    using (SaveFileDialog saveDialog = new SaveFileDialog())
    {
        saveDialog.Filter = "PNG 图片 (*.png)|*.png|JPEG 图片 (*.jpg)|*.jpg|BMP 图片 (*.bmp)|*.bmp";
        saveDialog.Title = "导出地图";
        
        if (saveDialog.ShowDialog() == DialogResult.OK)
        {
            Bitmap bmp = new Bitmap(mapPanel.Width, mapPanel.Height);
            mapPanel.DrawToBitmap(bmp, new Rectangle(0, 0, mapPanel.Width, mapPanel.Height));
            bmp.Save(saveDialog.FileName);
            statusLabel.Text = $"地图已导出到: {saveDialog.FileName}";
        }
    }
}

常见问题解决

1. 文件加载失败

  • 问题:找不到 .shx 或 .dbf 文件
  • 解决:确保 Shapefile 的三个文件 (.shp, .shx, .dbf) 在同一目录下

2. 中文乱码

  • 问题:属性表中的中文显示为乱码

  • 解决:设置正确的编码格式

    csharp 复制代码
    // 在 ShpReader 类中添加
    featureSet.Encoding = Encoding.GetEncoding("GB2312"); // 或 UTF-8

3. 性能问题

  • 问题:大型 Shapefile 加载缓慢
  • 解决
    • 使用空间索引
    • 实现要素简化
    • 分块加载

项目总结

这个 C# Shapefile 查看器提供了完整的地理数据可视化解决方案,具有以下特点:

  1. 完整的 Shapefile 支持

    • 读取和解析 SHP、SHX、DBF 文件
    • 支持所有标准几何类型
    • 处理属性数据
  2. 丰富的地图功能

    • 平滑的缩放和平移
    • 多种地图元素(比例尺、指北针、网格)
    • 图层管理
  3. 用户友好界面

    • 直观的控制面板
    • 状态显示
    • 响应式设计
  4. 可扩展架构

    • 模块化设计
    • 易于添加新功能
    • 支持自定义渲染
相关推荐
时光追逐者2 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
c#·.net·.netcore
CSharp精选营2 小时前
for和foreach到底谁快?刚子跑了1亿次循环,告诉你真相
c#·.net·foreach·for循环
雪人不是菜鸡14 小时前
反射调用方法
c#
unicrom_深圳市由你创科技19 小时前
C# 如何实现对象序列化
开发语言·c#
成都易yisdong20 小时前
实现三北方向转换计算器(集成 WMM2025 地磁模型)
开发语言·windows·算法·c#·visual studio
guygg8821 小时前
OPC UA Helper: 连接PLC获取变量值
服务器·网络·c#
成都易yisdong1 天前
基于C#和WMM2025模型的地磁参数计算器实现
开发语言·c#
预见AI1 天前
C#索引器练习题
开发语言·计算机视觉·c#