简介:这份资源面向具备一定.NET基础的GIS开发者与测绘、地信专业学生,提供ArcGIS与C#二次开发的完整工程源码,用于解决地图编辑、空间分析与网络分析等定制化需求。压缩包共282个文件,约911KB,以99个cs源码文件为核心,配合37个resx与resources资源文件、71个bmp位图及cur、ico等图标素材,另有csproj、sln工程文件与少量exe、dll,构成可直接编译运行的桌面GIS项目。内容围绕ArcEngine展开,涵盖点线面要素的创建、修改与删除,属性更新与版本管理,缓冲区、叠置等空间分析调用,以及网络数据集的路径选择与服务区划分,并涉及ArcGIS Server发布地图服务的思路。已有431人学习,适合作为课程设计、毕业设计或GIS工具开发的参考案例,帮助读者理解接口调用方式与工程组织逻辑,快速上手二次开发。
1. ArcGIS+C#二次开发:从地图编辑到网络分析的落地路线
如果你手头有一份 ArcGIS 的桌面授权,又恰好会写 C#,那这套组合能做的事情远比拖拽工具箱多得多。地图编辑、空间分析、网络分析这三个词放在一起,指向的是一类很具体的需求:把 ArcGIS 从“点按钮的软件”变成“嵌进业务流程里的引擎”。比如批量给几千条管线做连通性检查、按自定义规则自动打断线要素、算完最优路径后直接写回业务库——这些用 ArcMap 手动点,一天做不完,用 C# 调 ArcObjects 或 ArcGIS Pro SDK,几分钟跑完。
这份笔记面向的是已经装好 ArcGIS、写过一点 C#、但还没把两者真正接起来的人。我会按“先搞清楚用哪套 API、再动手写编辑和分析、最后把网络分析和坑讲透”的顺序推。热词里那些 arcgis安装10.8、arcgis pro 3.7 登录问题、arcgis裁剪影像 之类的,本质都是环境问题,环境不通后面全白搭,所以第 2 章先把选型和环境钉死。至于 c#高级编程、c#类库、c#线程 这些,会在具体代码里自然带出来,不单独讲语法。
2. 选型与环境:ArcObjects 还是 ArcGIS Pro SDK
2.1 两套 API 的边界,先想清楚再动手
ArcGIS 的 C# 二次开发有两条主线,选错了后面返工成本很高。
第一条是ArcObjects,绑定的是 ArcMap / ArcCatalog 那一代桌面产品(10.x)。它的能力最全,地图编辑、空间分析、网络分析全都有,缺点是 COM 组件、32 位、部署麻烦,而且 Esri 已经不再对它做功能更新。第二条是ArcGIS Pro SDK for .NET,绑定 ArcGIS Pro,基于 .NET 6/8,64 位,走的是现代插件框架,但它的 Network Analyst 能力要通过 ArcPy 或地理处理工具间接调,纯 SDK 直接操作网络数据集的接口没有 ArcObjects 那么细。
我一般的判断标准是这样的:
| 需求特征 | 推荐 | 原因 |
|---|---|---|
| 已有 ArcMap 10.x 授权,要深度操作网络数据集 | ArcObjects | INetworkDataset 等接口直接可用 |
| 新项目,要长期维护,界面要现代 | ArcGIS Pro SDK | .NET 生态,64 位,部署干净 |
| 只是批量跑分析、不需要交互界面 | ArcPy + C# 进程调用 | 最省事,绕开 COM |
| 要嵌入已有 WinForm/WPF 业务系统 | ArcObjects 或 Pro SDK 按授权定 | 看客户装的是哪个 |
热词里频繁出现的 arcgis安装10.8、arcgis安装license无法启动,基本都是 ArcObjects 路线的环境问题。10.8 是 ArcMap 最后一个大版本,装完之后 License 起不来,九成是 License Manager 服务没起或者防火墙拦了 27000 端口。这个后面避坑章细说。
2.2 环境搭起来的最小步骤
以 ArcObjects + Visual Studio 为例,走一遍能跑通的最小配置。
第一步,确认 ArcGIS Desktop 装好且能正常打开 ArcMap。打开不了就别往下走,先解决授权。
第二步,在 Visual Studio 里新建一个 .NET Framework 4.8 的类库或 WinForm 项目。注意必须是 .NET Framework,不能是 .NET Core/.NET 6+,ArcObjects 的 COM 互操作不支持后者。
第三步,添加引用。在“引用”里勾选 ESRI.ArcGIS 系列,常用的几个是:
ESRI.ArcGIS.System ESRI.ArcGIS.Geodatabase ESRI.ArcGIS.Carto ESRI.ArcGIS.Geometry ESRI.ArcGIS.DataSourcesFile ESRI.ArcGIS.DataSourcesGDB ESRI.ArcGIS.NetworkAnalyst ESRI.ArcGIS.Controls // 做界面才需要第四步,初始化许可。任何调用 ArcObjects 的程序,第一件事是绑许可,否则一调接口就抛异常。
using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS; // 在程序入口(Main 或窗体构造函数最前面)执行 private static void InitLicense() { // 单机版用 esriLicenseProductCodeEngine 或 ArcInfo // 这里用 ArcInfo,功能最全 IAoInitialize aoInit = new AoInitializeClass(); esriLicenseStatus status = aoInit.Initialize( esriLicenseProductCode.esriLicenseProductCodeArcInfo); if (status != esriLicenseStatus.esriLicenseCheckedOut) { throw new Exception("ArcGIS 许可初始化失败,状态:" + status); } // 程序退出时记得 aoInit.Shutdown() }这段代码的逻辑很直白:AoInitialize是 ArcObjects 的许可门卫,Initialize传入你要用的产品级别,返回状态。只有返回esriLicenseCheckedOut才算拿到许可。参数上,esriLicenseProductCodeArcInfo对应 ArcInfo 级别,如果你只有 ArcEditor 或 ArcView,换成对应枚举,但注意 Network Analyst 扩展需要额外CheckOutExtension。
// 网络分析扩展需要单独签出 status = aoInit.CheckOutExtension(esriLicenseExtensionCode.esriLicenseExtensionCodeNetwork); if (status != esriLicenseStatus.esriLicenseCheckedOut) { throw new Exception("Network Analyst 扩展签出失败"); }CheckOutExtension是很多人漏掉的一步。产品级许可拿到了,不代表扩展模块能用。现象就是代码编译没问题,一调INetworkAnalyst相关接口就报没有许可。参数esriLicenseExtensionCodeNetwork对应 Network Analyst,空间分析对应esriLicenseExtensionCodeSpatial,3D 对应esriLicenseExtensionCode3DAnalyst。
提示:许可签出是有数量限制的,调试时反复启动程序容易把许可占满,记得在
FormClosed或Main结束时调aoInit.Shutdown()释放。
3. 地图编辑与空间分析的代码落地
3.1 用 IFeatureCursor 做批量编辑,别用 IFeatureCursor 逐条 Update
地图编辑的核心对象是IFeatureClass和IFeature。新手最容易翻车的地方是逐条feature.Store(),几千条数据能跑几分钟。正确做法是用游标批量操作。
下面这段是给一个面要素类批量写入字段值的典型写法:
using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geometry; public void BatchUpdateField(IFeatureClass fc, string fieldName, object newValue) { // 用 IFeatureCursor 打开更新游标,第二个参数 false 表示非回收模式 IFeatureCursor cursor = fc.Update(null, false); IFeature feature = cursor.NextFeature(); // 找到目标字段的索引,循环外查一次,别在循环里查 int idx = fc.FindField(fieldName); if (idx < 0) throw new Exception("字段不存在:" + fieldName); while (feature != null) { feature.set_Value(idx, newValue); cursor.UpdateFeature(feature); // 关键:用游标更新,不是 feature.Store() feature = cursor.NextFeature(); } // 释放 COM 对象,否则文件锁不释放 System.Runtime.InteropServices.Marshal.ReleaseComObject(cursor); }逻辑说明:fc.Update(null, false)打开一个更新游标,null表示不过滤,false表示不用 recycling(回收模式会复用同一个对象,批量读快但写有坑)。FindField放在循环外是性能关键,每次循环查字段索引是常见的性能杀手。cursor.UpdateFeature比feature.Store()快,因为它走的是游标缓冲。
参数说明:Update的第一个参数是IQueryFilter,传null表示全表;第二个参数recycling在只读场景可以传true提速,但写场景必须false。Marshal.ReleaseComObject是 ArcObjects 的血泪经验,不释放游标,shapefile 或 gdb 会被锁住,下次打开就报“正在被其他程序使用”。
3.2 空间分析:用 IGeoProcessor 调工具还是纯接口
空间分析有两条路。一条是IGeoProcessor调现成的 GP 工具,比如 Buffer、Clip、Intersect;另一条是直接用ITopologicalOperator等几何接口自己算。前者省事,后者快且可控。
调 GP 工具的写法:
using ESRI.ArcGIS.Geoprocessor; using ESRI.ArcGIS.AnalysisTools; public void RunBuffer(string inputPath, string outputPath, double distance) { Geoprocessor gp = new Geoprocessor(); gp.OverwriteOutput = true; // 允许覆盖同名输出 // Buffer 工具的参数按顺序塞 Buffer bufferTool = new Buffer(inputPath, outputPath, distance.ToString()); IGeoProcessorResult result = (IGeoProcessorResult)gp.Execute(bufferTool, null); if (result.Status != esriJobStatus.esriJobSucceeded) { // 拿详细报错信息,别只看状态 for (int i = 0; i < gp.MessageCount; i++) Console.WriteLine(gp.GetMessage(i)); } }逻辑说明:Geoprocessor是 GP 工具的托管包装,Execute同步执行。OverwriteOutput = true很关键,否则输出已存在时直接失败。参数按工具定义的顺序传,Buffer是(输入、输出、距离)。
参数说明:distance.ToString()这里没带单位,如果输入数据是地理坐标系(度),距离单位就是度,结果会离谱。正确做法是确保输入是投影坐标系,或者距离字符串带单位如"1000 Meters"。这是空间分析最常见的翻车点之一,热词里 arcgis做坡度图、arcgis裁剪影像 都涉及坐标系问题。
如果只是做简单的几何运算,比如判断相交、求并集,直接用接口更快:
// 判断两个几何是否相交 ITopologicalOperator topoOp = (ITopologicalOperator)geometry1; if (topoOp.IsSimple == false) topoOp.Simplify(); // 先简化,否则结果可能异常 bool intersects = topoOp.Intersects(geometry2);Simplify()这一步很多人省掉,结果Intersects返回的结果时对时错,属于典型的玄学问题。原因是几何本身有自相交或未闭合,拓扑操作前必须先简化。
4. 网络分析:从数据集构建到路径求解
4.1 网络数据集必须先建好,代码只是调用方
网络分析的前提是有一个建好的 Network Dataset。这一步通常在 ArcCatalog 里手动建,或者用 Python 脚本建。C# 代码能做的是加载它、设置分析参数、求解。
加载网络数据集:
using ESRI.ArcGIS.NetworkAnalyst; using ESRI.ArcGIS.Geodatabase; public INetworkDataset LoadNetworkDataset(IWorkspace workspace, string datasetName) { // 找到网络数据集所在的 feature dataset IFeatureWorkspace fw = (IFeatureWorkspace)workspace; IDatasetContainer3 dc = (IDatasetContainer3)fw; // 遍历找目标网络数据集 for (int i = 0; i < dc.DatasetCount; i++) { IDataset ds = dc.get_Dataset(i); if (ds.Name == datasetName && ds is INetworkDataset) return (INetworkDataset)ds; } throw new Exception("未找到网络数据集:" + datasetName); }逻辑说明:网络数据集在 gdb 里是特殊对象,不能当普通 feature class 打开。IDatasetContainer3是容器接口,遍历找到INetworkDataset。参数datasetName是网络数据集的名字,不是路径。
拿到INetworkDataset后,要创建INAContext才能做分析:
// 创建分析上下文 INetworkDataset nds = LoadNetworkDataset(workspace, "RoadNetwork"); INAContext naContext = new NAContextClass(); naContext.Initialize(nds); // 设置求解器为路径分析 INASolver naSolver = new NARouteSolverClass(); naContext.Solver = naSolver;NAContext是网络分析的会话对象,Initialize绑定数据集,Solver决定做哪类分析。路径分析用NARouteSolverClass,服务区分析用NAServiceAreaSolverClass,最近设施用NAClosestFacilitySolverClass。
4.2 路径求解的完整流程与参数
路径分析要设停靠点、求解、读结果。停靠点用NAClass承载:
// 创建停靠点要素类 INAClass naClass = naContext.CreateClass("Stops"); naContext.Classes.Add(naClass); // 添加两个停靠点 IFeatureClass stopsFC = naClass.FeatureClass; IFeature stop1 = stopsFC.CreateFeature(); stop1.Shape = point1; // point1 是 IPoint stop1.Store(); IFeature stop2 = stopsFC.CreateFeature(); stop2.Shape = point2; stop2.Store(); // 求解 naContext.Solver.Solve(naContext, null, null); // 读结果 INAClass routeClass = naContext.Classes.get_ItemByName("Routes"); IFeatureCursor cursor = routeClass.FeatureClass.Search(null, false); IFeature route = cursor.NextFeature(); // route.Shape 就是路径几何逻辑说明:CreateClass创建分析用的临时要素类,Stops是停靠点,Routes是结果。停靠点必须Store()之后求解器才能读到。Solve的第二个参数是IGPMessages,传null表示不收集消息,调试时建议传一个对象进去看求解日志。
参数说明:路径分析有几个必调参数,通过INAContext的属性设置:
| 参数 | 设置方式 | 说明 |
|---|---|---|
| 阻抗属性 | naSolver.ImpedanceAttributeName | 用哪个字段当成本,如 Length、Time |
| 是否允许 U 形转弯 | naSolver.UTurnPolicy | 默认不允许,物流场景常要允许 |
| 输出路径几何 | naSolver.OutputGeometryPrecision | 精度太高结果大,一般 1 米够用 |
| 停靠点顺序 | naSolver.FindBestSequence | 是否求解最优顺序,TSP 场景要开 |
ImpedanceAttributeName设错是最常见的翻车点。网络数据集里可能同时有 Length 和 DriveTime 两个成本字段,设成 Length 算出来的是最短距离,设成 DriveTime 才是最快时间。现象就是结果和预期对不上,但代码不报错。
注意:
Solve是同步阻塞的,网络数据集大、停靠点多的时候会卡住界面。放在后台线程里跑,用BackgroundWorker或Task,别在主线程直接调。
5. 避坑与排查:那些让程序跑不起来的细节
5.1 许可初始化失败,报“没有许可”
现象:程序启动就抛异常,提示许可状态是esriLicenseNotLicensed或esriLicenseUnavailable。
原因:三种可能。一是 ArcGIS License Manager 服务没启动;二是防火墙拦了 27000 端口;三是产品级别枚举选错了,比如装的是 ArcView 却申请 ArcInfo。
解决:先开服务管理器确认ArcGIS License Manager在运行。再检查防火墙,27000 端口要放行。最后核对esriLicenseProductCode枚举和实际授权是否匹配。热词里 arcgis安装license无法启动 基本就是这个链路。
5.2 游标不释放,文件被锁
现象:程序跑完,gdb 或 shapefile 打不开,提示“正在被其他程序使用”。
原因:IFeatureCursor、IWorkspace这些 COM 对象没释放,进程还占着文件句柄。
解决:每个游标用完调Marshal.ReleaseComObject(cursor),工作空间用完调Marshal.ReleaseComObject(workspace)。更稳妥的做法是把这些操作包在try...finally里,finally 里释放。别指望 GC 自动回收,COM 对象的生命周期和 .NET 托管对象不是一回事。
5.3 空间分析结果为空或范围异常
现象:Buffer 或 Clip 跑完,输出要素类存在但里面没数据,或者范围大得离谱。
原因:坐标系不一致。输入数据是地理坐标系(度),距离参数按米给,结果要么小到看不见,要么大到覆盖全球。
解决:分析前统一投影。用IGeometry.Project转到投影坐标系,或者确保输入数据本身就是投影坐标。热词里 arcgis裁剪影像、arcgis做坡度图 遇到的结果异常,先查坐标系。
5.4 网络分析求解返回空路径
现象:停靠点加进去了,Solve也执行了,但 Routes 里没要素。
原因:停靠点不在网络数据集的可达范围内。网络数据集有连通性设置,如果停靠点落在没有连通的边上,求解器找不到路径。
解决:加停靠点前先做位置捕捉,用INALocator把点吸附到最近的网络边上。另外检查网络数据集的连通性策略,端点连通和任意折点连通结果差别很大。
5.5 32 位进程内存溢出
现象:处理大数据量时程序崩溃,报OutOfMemoryException。
原因:ArcObjects 是 32 位 COM,进程内存上限约 2GB,大数据量游标遍历很容易撑爆。
解决:分批处理,用IQueryFilter加WhereClause分页读。或者改用 ArcGIS Pro SDK 走 64 位路线。这是 ArcObjects 的硬伤,没有银弹。
6. 进阶技巧:把网络分析和业务数据串起来
走到这一步,单次路径求解已经不是问题了。真正有价值的是把网络分析和业务库联动,做成一个能反复跑的服务。我一般会这么做:用 C# 读业务库(SQL Server 或 Access)里的订单和车辆表,动态生成停靠点,跑完路径分析后把结果写回业务库。
// 从业务库读订单,生成停靠点 string connStr = "Data Source=.;Initial Catalog=Logistics;Integrated Security=True"; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); SqlCommand cmd = new SqlCommand( "SELECT OrderId, Lon, Lat FROM Orders WHERE Status = 'Pending'", conn); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { IPoint pt = new PointClass(); pt.PutCoords((double)reader["Lon"], (double)reader["Lat"]); // 用 pt 创建停靠点,加到 naClass } }逻辑说明:业务库的经纬度直接构造IPoint,但注意坐标系要和网络数据集一致。如果网络数据集是投影坐标,这里要先做坐标转换,否则停靠点位置全错。
参数说明:PutCoords传的是 X(经度)和 Y(纬度),顺序别反。数据库连接串按实际环境改,Access 用Provider=Microsoft.ACE.OLEDB.12.0。
一个我踩过的坑:批量求解时,每次Solve都新建NAContext会越来越慢,因为网络数据集每次都要重新加载。正确做法是复用同一个NAContext,只清空停靠点重新加。清空用naContext.Classes.get_ItemByName("Stops").FeatureClass.DeleteSearchedRows(null)。
验证结果对不对,我习惯用两个手段。一是把路径几何导出成 shp,叠到底图上肉眼扫一遍;二是对比阻抗值,手动算一条已知路径的距离,和求解器结果比,误差在 5% 以内算正常。超过这个范围,回去查阻抗属性设置和连通性。
这套东西值不值得投入,取决于你的业务里“路径”和“空间关系”出现的频率。如果每天要算几百条路径、或者编辑操作是重复性的,那写一次代码省下的人力很可观。如果只是偶尔算一次,ArcMap 手动点更快。我自己的习惯是,任何要重复三次以上的空间操作,就值得写成 C# 工具。希望帮到你。
本文还有配套的精品资源,点击获取