ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ArcGIS Engine与C#二次开发实战:地图编辑、空间分析与网络分析

ArcGIS Engine与C#二次开发实战:地图编辑、空间分析与网络分析 简介这是一份面向GIS开发学习者的ArcGISC#二次开发实例资源包围绕ArcEngine与ArcGIS Server环境下的地图编辑、空间分析、网络分析三大模块展开。压缩包共282个文件、约911KB其中99个cs源码文件配合sln/csproj工程结构构成了可编译的完整开发骨架37组resources/resx资源、71张bmp按钮图标及光标、ico、exe等文件覆盖了界面显示与运行所需的辅助资源便于读者直接加载项目查看效果并二次修改。资源已有431人学习下载。包内示例涉及撤销、保存数据、删除要素、增加注记、视图缩放与平移等地图编辑工具同时包含数据管理、空间分析与网络分析的关键实现能够让开发者直观看到如何在C#中调用ArcGIS接口完成几何对象操作、图层维护与服务发布。对希望入门ArcEngine桌面端开发或尝试ArcGIS Server Web GIS的IT从业者来说这是一份体量紧凑、结构清晰且极具参考价值的实战案例。1. 拿到这个 rar 之前先想清楚 ArcGIS C# 到底在解决什么问题做 GIS 桌面端开发的人手里经常会攒着这样一个 rar标题写着“ArcGISC#二次开发”压缩包里是地图编辑、空间分析、网络分析三块功能。它解决的不是“怎么画一张图”而是“怎么让业务人员在一个 Windows 程序里完成矢量数据的编辑、查询、缓冲区分析和路径规划”。这套东西在测绘、规划、管线、国土类项目里需求量很大企业要的往往不是一个 ArcMap 的替代品而是一个能把 GIS 能力嵌进自己业务系统、又不用用户去学 ArcGIS 操作的桌面工具。适合刚接手 GIS 开发任务、手头有 ArcGIS Engine 许可、想快速搭出可用原型的开发者。一个反直觉的事实是真正难住大家的不是空间分析和网络分析的算法而是 AO 接口的许可初始化、COM 生命周期和图层数据源那几步这三步不踏实后面写得再顺也是白搭。2. 工程搭建与许可初始化为什么第一步就决定后面能不能跑起来2.1 环境选型ArcGIS Engine 10.x VS 的搭配逻辑做 ArcGIS C# 二次开发首先要明确走的是 ArcGIS Engine 这条路而不是 ArcGIS Pro 的 Add-in。Pro 的 Add-in 是 .NET 6 之上的新体系用的 API 和地图模型都和 Engine 不同而这个 rar 标题里写的 C# 二次开发基本锁定在 ArcGIS Engine 10.x 的 AOArcObjects体系。常见做法是用 ArcGIS Engine 10.2 或 10.8 开发包配上 Visual Studio 2015/2017/2019 和 .NET Framework 4.x 做 WinForms 或 WPF 程序。开发时引用 ESRI.ArcGIS 系列程序集部署时目标机器装 ArcGIS Engine Runtime 即可。选择 ArcGIS Engine 而不是直接让用户装 ArcMap原因是可控的Engine 只提供运行库和组件不提供桌面界面你的程序就是唯一入口不会再弹出一堆 ArcMap 的菜单和工具。代价是开发量上来了尤其是图层符号化、比例尺、坐标系统这些交互AO 接口的粒度比 Pro 的 Add-in 细很多每条都要自己写回调。这套选型在企业里还有一个现实原因很多项目甲方手里已经有 10.x 的许可版本迁移成本高新活接在老技术上反而是最稳的。2.2 许可初始化的顺序代码第一行就该 BindArcGIS Engine 的程序有一个铁律第一个 AO 对象创建之前必须先绑定运行时版本和产品码。很多新手把new AOInitializeClass()写在项目入口第一行然后就开始 new 各种控件一运行就报 “Class not registered” 或 “Failed to create object”。这不是控件没装好而是 Bind 的顺序不对。ArcGIS Engine 10.x 里初始化代码标准写法是using ESRI.ArcGIS; using ESRI.ArcGIS.esriSystem; // 绑定运行时必须放在 new 任何 AO 对象之前 RuntimeManager.Bind(ProductCode.Engine); // 初始化许可 IAoInitialize aoInit new AoInitializeClass(); esriLicenseProductCode product esriLicenseProductCode.esriLicenseProductCodeEngine; // 先检查许可是否可用再初始化 if (aoInit.IsProductLicensed(product) esriLicenseStatus.esriLicenseStatusAvailable) { aoInit.Initialize(product); } else { throw new Exception(Engine 许可不可用请检查许可服务); }这段代码的逻辑是RuntimeManager.Bind负责把托管代码绑定到本机安装的 ArcGIS Engine Runtime 上它不认识ProductCode.Engine以外的产品如果你 bind 成了ProductCode.Desktop而机器上只装了 Engine Runtime后面照样报错。IsProductLicensed是许可校验返回值是esriLicenseStatus枚举只有Available才能继续初始化。Initialize之后才允许创建 MapControl、FeatureClass 这类重量级对象。整个 App 生命周期里这段初始化执行一次就够了不需要每个窗体都重复调。这里有个容易翻车的点很多人用 ArcGIS Engine 开发包写代码但部署的时候机器上只装了 Desktop 的许可服务没有单独给 Engine Runtime 配许可程序启动时IsProductLicensed就会返回NotLicensed。这个错误在开发机上出现频率极高因为开发机往往既装了 Desktop 又装了 Engine默认可用换到干净的部署机上马上就露馅。所以这个检查分支一定不能省略要写一个明确的异常提示方便现场排查。2.3 程序集引用与 x86 平台一个决定生死的编译开关ArcGIS Engine 10.x 的 ESRI.ArcGIS 系列程序集是 32 位 COM 组件包在 Visual Studio 里新建工程后默认的AnyCPU目标平台会让 64 位模式下加载失败报错信息要么是 “BadImageFormatException”要么是控件无法拖入设计器。常见的做法是把项目的目标平台强制改成 x86项目属性 → 生成 → 目标平台 → x86。这个设置对 WinForms 和 WPF 都适用别指望用 AnyCPU 碰运气碰不过去的。引用哪些程序集也有讲究。一个做地图编辑 空间分析 网络分析的工程最少要引这几个命名空间程序集用途ESRI.ArcGIS.ControlsESRI.ArcGIS.Controls.dllMapControl、ToolbarControl、TOCControlESRI.ArcGIS.CartoESRI.ArcGIS.Carto.dllMap、Layer、FeatureLayer 等地图对象ESRI.ArcGIS.GeodatabaseESRI.ArcGIS.Geodatabase.dll访问要素类、工作空间、编辑会话ESRI.ArcGIS.GeometryESRI.ArcGIS.Geometry.dll点线面几何、空间关系计算ESRI.ArcGIS.DisplayESRI.ArcGIS.Display.dll符号、颜色、渲染ESRI.ArcGIS.SpatialAnalystESRI.ArcGIS.SpatialAnalyst.dll空间分析工具ESRI.ArcGIS.NetworkAnalystESRI.ArcGIS.NetworkAnalyst.dll网络分析工具右键引用 → 添加引用 → 浏览到 Engine 安装目录下的com文件夹比如C:\Program Files (x86)\ArcGIS\Engine\com就能看到这些托管程序集。注意不要从 GAC 里乱引版本号要跟开发包一致。引用完之后还有个隐藏步骤把每个引用的“复制本地”设为 true发布的时候这些 dll 会一起考到输出目录部署机就不用额外装开发包了。3. 地图编辑功能实现从编辑会话到增删改与撤销3.1 编辑会话的开启为什么不能直接改 FeatureArcGIS 的矢量数据编辑跟普通数据库 UPDATE 完全不是一个套路。你在同一个工作空间里开多个编辑会话、或者在没开会话的情况下直接对要素类做增删改轻则崩溃重则数据锁死。AO 体系下编辑操作必须包在一个编辑会话里IEngineEditor接口是核心它管理当前编辑的工作空间、图层和操作栈。开启会话的代码using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Controls; // 地图里取第一层作为编辑图层 IFeatureLayer featureLayer mapControl.Map.get_Layer(0) as IFeatureLayer; if (featureLayer null) return; IEngineEditor engineEditor new EngineEditorClass(); // 第二步开始编辑会话传入地图和可编辑图层 if (!engineEditor.EditSession) { engineEditor.StartEditing(mapControl.Map, featureLayer); // 开启一个操作步骤后续的改动都在这一步里可整体撤销 engineEditor.StartOperation(); }这里的逻辑有三层。第一StartEditing的第一个参数是IMap第二个参数可以是ILayer也可以是IWorkspace传图层时编辑器只锁定该图层所在数据源的范围。第二编辑器对象全局只需要一个实例整个窗体生命周期复用不要每编辑一次就 new 一次否则之前的撤销栈就丢了。第三StartOperation声明一个操作的开始操作是编辑栈的基本单位所有在这个操作里产生的数据改动会被合并成一个待撤销项。3.2 增删改要素三种写法背后的适用场景要素新增的核心代码是CreateFeature 赋值 Store但不开编辑会话直接写也能成功区别在于没有撤销栈和冲突检测。在实际项目里散点新增、手绘多边形、批量导入这三种场景要用三种写法散点手绘用编辑会话里的IFeature.Store()批量导入数据用IFeatureCursor逐条Insert图层叠加到目标图层时用IFeatureEdit做合并。散点新增的完整代码// 获取要素类创建要素 IFeatureClass featureClass featureLayer.FeatureClass; IFeature feature featureClass.CreateFeature(); // 构造一个点几何坐标来自鼠标点击或表格 IPoint point new PointClass(); point.X 116.3; point.Y 39.9; point.SpatialReference featureClass.SpatialReference; // 给字段赋值 feature.Shape point; feature.set_Value(featureClass.FindField(NAME), 测试点); feature.Store();CreateFeature只是在内存里创建一个要素实例Store才会真正写入数据源。赋值字段时不要用set_Value(0, ...)这种硬编码索引数据表结构一变动索引就失效先用FindField拿字段索引。SpatialReference的设置容易忽略点没有坐标系就加入图层ArcMap 可能会动态投影显示位置偏差尤其在多图层不同坐标系时非常明显。要素删除的代码比较简单但有个细节删除前要先Select拿要素 OID不要用遍历集合边删边取的方式因为 AO 的要素集合在删除过程中会锁定游标正确的做法是先收集要删的要素 OID 列表再统一删除。要素修改则要记住feature.Store()之后调用编辑器的StopOperation结束当前操作否则撤销列表里永远只有一笔。3.3 撤销与保存的边界StopEditing(true) 不是后悔药ArcGIS Engine 的撤销栈只存在于编辑会话中。StartOperation开始的操作在StopOperation后进入撤销栈engineEditor.Undo()可以往前回退回退完想要再前进用Redo()。但这里有一个常见的认知偏差撤销栈的内存存储只在你调用StopEditing之前有效。如果你直接调用StopEditing(true)保存并结束会话编辑操作已经落盘栈被清空没有任何后悔药可吃。遇到用户误删了一大片数据你只能从备份或版本管理里捞。所以一个稳妥的做法是在StopEditing前弹一个确认框明确告诉用户“编辑内容将写回数据源且撤销记录会清空”。同时在会话外也可以手动备份一份数据常见方案是IFeatureClassCopy复制原始要素类到内存工作空间作为会话级后悔药。这个备份只在当前会话存活会话结束释放不会污染磁盘。还有一个细节是StartEditing之后图层如果被Remove或地图刷新MapControl.Refresh()编辑器持有的图层引用可能失效再次编辑时报“未指定图层”。遇到这种情况检查engineEditor.EditState esriEditState.esriEditStateNotEditing如果不是就先把编辑会话关掉再刷新地图。4. 空间分析与网络分析两个功能一条路4.1 空间分析缓冲区和叠加分析的参数怎么设标题里的空间分析最常落地的功能是缓冲区分析、裁剪和叠加分析。AO 里空间分析有两个入口一个是IBuffer接口的几何级操作一个是Geoprocessor跑 GP 工具。几何级操作处理的是内存里的IGeometry不依赖数据源适合小数据量实时计算GP 工具会调起后台进程处理要素类适合大批量数据。写一个小规模缓冲区分析用几何级操作就够了using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.Carto; IFeatureLayer sourceLayer mapControl.Map.get_Layer(1) as IFeatureLayer; IFeatureCursor cursor sourceLayer.FeatureClass.Search(null, false); IFeature feature null; // 遍历要素逐个生成缓冲区并合并 IGeometry unionGeometry null; while ((feature cursor.NextFeature()) ! null) { ITopologicalOperator topoOperator feature.Shape as ITopologicalOperator; // 缓冲半径 100 米负号表示地理缓冲相对坐标系正数表示线性缓冲 IGeometry bufferGeometry topoOperator.Buffer(100.0); if (unionGeometry null) { unionGeometry bufferGeometry; } else { ITopologicalOperator unionOp unionGeometry as ITopologicalOperator; unionGeometry unionOp.Union(bufferGeometry); } }这里最有迷惑性的参数是Buffer的输入值是 double 类型单位取决于图层坐标系。如果图层是地理坐标系经纬度Buffer(100)意味着 100 度结果会把整个地球圈进去如果是投影坐标系100 就是 100 米。这在真实项目里是常见翻车点用 WGS84 的经纬度数据直接缓冲出来的图形大得离谱。还有一个相关坑是代码里写的是“100米”但数据源的坐标系是度结果就是错的。稳妥做法是先检查layer.FeatureClass.SpatialReference是投影还是地理坐标系再决定传入的数值要不要换算。叠加分析如裁剪在 AO 里用ITopologicalOperator.Intersect实现两个几何对象求交用法和Buffer类似。大数据量的裁剪我一般直接用Geoprocessor跑Clip工具因为几何级操作在百万级要素下内存开销相当大容易卡死界面。做空间分析时还有一个必调参数是空间参考匹配参与运算的两个几何必须处于相同的投影坐标系否则 AO 没有自动重投影结果为空或形状错乱。4.2 网络分析最短路径与网络数据集的调用链网络分析和空间分析在 ArcGIS 里是两个体系。空间分析是几何图形的集合运算网络分析是基于网络数据集的图论求解。要做最短路径数据准备就必须先有一个网络数据集或几何网络通常由 Shapefile 的线图层构建在 ArcMap/Catalog 里右键建好Network Dataset之后代码才能用。常见的IRouteSolver调用链using ESRI.ArcGIS.NetworkAnalyst; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.Carto; // 前提当前地图里有一个网络数据集图层 INetworkLayer networkLayer mapControl.Map.get_Layer(2) as INetworkLayer; if (networkLayer null) return; INetworkDataset networkDataset networkLayer.NetworkDataset; // 创建路径求解器并绑定网络分析环境 IRouteSolver3 routeSolver new RouteSolverClass(); INetworkAnalysisExt3 analysisExt new NetworkAnalysisExtClass(); analysisExt.NetworkDataset networkDataset as INetworkDataset; routeSolver.NetworkAnalysis analysisExt as INetworkAnalysisExt; // 添加两个停靠点起点和终点 INetworkAnalysisExt3 ext routeSolver.NetworkAnalysis as INetworkAnalysisExt3; IStop stop1 ext.Stops.CreateStop(); stop1.Point startPoint; IStop stop2 ext.Stops.CreateStop(); stop2.Point endPoint; ext.Stops.AddItem(stop1, 0); ext.Stops.AddItem(stop2, 1); // 求解路径 ISolveContext solveContext routeSolver.Solve(null);这段代码里参数的重点有四个。一是NetworkDataset不能为 null如果图层是普通线图层而不是网络数据集图层这里就直接空引用所以要用上面的判空。二是两个停靠点的 Z 值和 M 值必须为 0网络分析是平面拓扑求解带上高程值会导致求解器认为两点不可达。三是Solve的返回值是ISolveContext里面有求解状态和路径几何不要直接跳过它去取routeSolver.Results因为结果可能为空但Solve没有抛异常。四是网络分析需要一个单独的扩展许可esriLicenseExtensionCode.esriLicenseExtensionCodeNetwork在初始化阶段就要InitializeExtension忘了这步运行时倒还不会报错但Solve会静默返回失败状态码写的是“未授权扩展”。实际项目中网络分析还有两个很实用的分支一是服务区分析把RouteSolver换成ServiceAreaSolver参数一般调“默认断点”为阻抗值比如 10 分钟输出是一个面状几何表示从起点出发在阻抗范围内能覆盖的区域二是最近设施分析把 Stops 换成 Facilities 和 Incidents可以求解“离我最近的 3 个加油站”。这些参数在 AO 里都能映射到ClosestFacilitySolver的DefaultCutoff上。5. 常见问题与避坑这 4 个坑我几乎每个项目都踩过5.1 点启动没反应许可服务静默失败的典型现象程序双击启动进程存在但没有窗口或者弹一个“产品未授权”的提示框后退出。原因绝大多数情况是目标机器的 ArcGIS License Manager 服务没启动或者开发机装了 Desktop 但部署机只装 Engine RuntimeLicense 文件并没有正确绑定。另一个隐蔽原因是机器没装 .NET Framework 3.5AO 10.x 的部分本地组件依赖 .NET 3.5 的 CLRWin10/11 默认不装。解决部署机按顺序重装三件套——.NET Framework 3.5控制面板 → 启用或关闭 Windows 功能 → 勾选 .NET Framework 3.5再装 ArcGIS Engine Runtime最后启动 License ManagerArcGIS 安装目录下的LicenseManager.exe。装完后不要急着跑程序先检查服务状态里有没有ArcGIS License Manager这个服务没有就手动启动。这一步 90% 的启动失败都能消掉。5.2 初始化 AO 报 “Failed to create object”x86 和 Bind 顺序的锅现象new AoInitializeClass()或new MapControlClass()抛COMException说创建对象失败。原因两个层面。一是项目编译目标是 AnyCPU程序跑在 64 位模式而 AO 组件是 32 位 COM加载不上二是RuntimeManager.Bind没有在第一次 new AO 对象之前执行导致 AO 不知道你要用哪个产品码。解决项目属性 → 生成 → 目标平台改成 x86同时把RuntimeManager.Bind(ProductCode.Engine)提到Program.Main方法的第一行注意是任何newAO 对象之前。如果已经写了 Bind 还在报错检查 Bind 的参数是Engine还是EngineGeoDB装的是 Engine 基础版就 bind Engine装了 GeoDatabase 扩展就 bind EngineGeoDB反了也会失败。把这两步做完问题基本绝迹。5.3 地图编辑无法保存要素类没有版本化或锁冲突现象engineEditor.StopEditing(true)抛异常提示“无法在编辑会话期间提交”或编辑过程中界面卡死再次操作时弹“要素类被锁定”。原因目标数据是 SDE 地理数据库里的非版本化数据或者有其他程序ArcMap、另一个窗体已经打开了编辑会话。AO 的编辑会话是单例的不允许多个IEngineEditor同时启动。非版本化数据的编辑其实也能做但要求工作空间是独占锁一旦有别的连接在访问就会抛锁冲突。解决操作前先engineEditor.StopEditing(false)清理残留会话再重新StartEditing。如果反复被锁打开 ArcMap 里的图层属性 → 源确认数据源是文件型 GDB 还是 SDE文件 GDB 一般不存在锁冲突问题SDE 就要检查当前有没有其他用户在连接。遇到“无法在编审期间提交”这个具体报错多半是你对版本化图层做了非版本化的编辑操作解决问题的路径是检查图层是否RegisteredAsVersioned不是版本化图层就用IWorkspaceEdit手动提交。5.4 网络分析求解结果为空权值字段和连通性设置导致现象Solve执行成功返回的路径几何为 null或者路径只连接了两个点的直线完全不顺着路网走。原因网络数据集没有设置阻抗属性如长度、时间或者网络数据的连通性策略设置成“端点连接”而路网数据在现实中有立交桥、跨线桥物理上点不重合导致网络不连通。求解器在没有阻抗的情况下会默认用一个恒定权值结果就变成了两点直线。解决回到 ArcMap/Catalog 里打开网络数据集属性检查“连通性”是不是“端点连接”如果是把道路在交叉口断开的逻辑处理掉或者改成“端点连接并允许折点连接”。同时给网络数据集添加一个“长度Length”属性作为默认阻抗成本字段选 Meters。改完重建网络数据集重新加载到地图再跑Solve就正常了。5.5 部署版报 “Class not registered”开发包与 Runtime 版本不一致现象开发机上跑程序一切正常拷到部署机装完 Runtime 后启动即报“未注册类”定位到某一行new对象的代码。原因开发时引用的 ESRI.ArcGIS 程序集版本是 10.8部署机装的 Runtime 是 10.2COM 组件的 CLSID 不匹配。AO 的程序集强命名版本和 Runtime 版本强绑定不能混用。另一个原因是“复制本地”没有把引用的 100 多个 ESRI dll 全部带过去少了一些运行时才加载的组件。解决部署机的 Runtime 版本必须和开发包的大版本一致10.8 开发包就装 10.8 Runtime补丁级别最好也对上。发布时把“复制本地”全部设为 true再检查一下发布目录里有没有ESRI.ArcGIS.System.dll和ESRI.ArcGIS.Version.dll这两个依赖最重的文件没有就从开发机的com文件夹拷一份。最后在部署机上跑一次ESRI 许可管理员确认 Engine 许可状态为“可用”而不是“未检测到许可”。6. 进阶技巧用 Geoprocessor 交叉验证结果并记录编辑日志功能都调通了之后最容易被忽略的是验证和审计。我自己的习惯是每个空间分析和网络分析的结果都用 Geoprocessor 重跑一次同参数的 GP 工具做对拍。比如自己用ITopologicalOperator.Buffer算出来的缓冲区面再调Geoprocessor.Execute(Buffer)跑一次比对两个结果的面积和要素数偏差在 1% 以内才算通过。这个验证步骤能发现坐标系单位选错、拓扑点序错误这些肉眼看不出来的细节。网络分析同理用 ArcMap 的 Network Analyst 窗口手动求解一次和程序的结果对比路径长度能筛掉路网方向字段配错导致的绕路问题。编辑日志的建议也值得做在StartOperation和StopOperation之间用ITransactionLogger或最简单的文件日志记录每一次操作的要素类型和 OID。做应急恢复时这个日志比任何备份都好用。最后提醒一个老生常谈的细节AO 的 COM 对象是托管包装的非托管资源用完要释放特别是IFeatureCursor不释放会让数据源一直处于占用状态后续编辑锁冲突十有八九是它引起的。写这么多年 ArcGIS Engine我在这上面翻车最多次希望帮到你。本文还有配套的精品资源点击获取
返回列表