用于读取 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)
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)
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 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 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. 操作步骤
- 运行程序
- 点击"打开 shapefile"按钮
- 选择要打开的 .shp 文件
- 使用鼠标滚轮缩放地图
- 点击"平移模式"后拖拽地图
- 使用控制面板调整显示选项
3. 支持的 shapefile 类型
- 点 (point)
- 线 (polyline)
- 面 (polygon)
- 多点 (multipoint)
- 多线 (multipolyline)
- 多面体 (multipolygon)
参考代码 学会用c#文件读取的shp(shapefile格式)文件并在窗口绘制 www.youwenfan.com/contentcst/39627.html
技术实现细节
1. shapefile 读取
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. 坐标变换
// 计算缩放比例 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. 几何绘制
// 点绘制 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. 添加属性查询功能
// 在 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. 添加空间查询功能
// 在 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. 添加图层样式编辑
// 在 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. 添加导出图片功能
// 在 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. 中文乱码
- 问题:属性表中的中文显示为乱码
- 解决:设置正确的编码格式
// 在 shpreader 类中添加
featureset.encoding = encoding.getencoding("gb2312"); // 或 utf-8
3. 性能问题
- 问题:大型 shapefile 加载缓慢
- 解决:
- 使用空间索引
- 实现要素简化
- 分块加载
项目总结
这个 c# shapefile 查看器提供了完整的地理数据可视化解决方案,具有以下特点:
- 完整的 shapefile 支持:
- 读取和解析 shp、shx、dbf 文件
- 支持所有标准几何类型
- 处理属性数据
- 丰富的地图功能:
- 平滑的缩放和平移
- 多种地图元素(比例尺、指北针、网格)
- 图层管理
- 用户友好界面:
- 直观的控制面板
- 状态显示
- 响应式设计
- 可扩展架构:
- 模块化设计
- 易于添加新功能
- 支持自定义渲染
到此这篇关于c# 读取和绘制 shapefile (shp) 文件的详细过程的文章就介绍到这了,更多相关c# 读取 shapefile文件内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论