☰
TerraExplorer二次开发C#示例:三维GIS场景联动与COM互操作实战
2026/10/17 2:54:20 网站建设 项目流程

简介:这份资源是面向GIS与三维可视化开发者的TerraExplorer二次开发C#示例代码包,基于Skyline平台,适合已掌握C#基础语法、希望快速上手TerraExplorer SDK的初中级开发者。它解决了从零搭建三维地球应用时缺少可运行参考的问题,覆盖事件驱动编程、地图对象与控件操作、Shapefile与KML等数据集成、3D模型加载、空间查询与缓冲区分析、自定义界面及多线程优化等典型场景。压缩包共21个文件,约42KB,以cs源码为主,辅以resx资源文件、csproj与sln工程文件,并附带shp、dbf、shx、prj等Shapefile样例数据及fly飞行文件,可直接在Visual Studio中打开编译运行。目前已有483人学习下载。通过研读这些示例,读者能掌握TerraExplorer API的调用方式、地图与图层对象的组织逻辑、空间分析实现思路以及调试优化技巧,为定制化三维GIS项目提供可复用的代码骨架与排错参考。

1. 从一次三维场景联动需求说起:这套 C# 示例代码到底能干什么

有个做 GIS 可视化的朋友接过一个活:客户要在三维地球上叠加实时轨迹、点击模型弹信息框、还要把相机飞到指定坐标。他一开始打算纯前端硬啃 WebGL,结果发现客户现场用的是一套成熟的三维地球客户端,支持用 C# 做插件式扩展。他翻了两天文档没跑通第一个弹窗,最后靠一份二次开发示例代码把链路打通了。这份 TerraExplorer 二次开发 C# 示例代码,解决的就是这类问题——它把三维场景的加载、图层控制、对象创建、相机操作、事件响应这些高频动作,用 C# 封装成可调用的接口示例。适合两类人:一是手里有三维地球客户端、需要做定制功能集的桌面端开发者;二是想从零理解三维 GIS 二次开发接口设计思路的 C# 工程师。下面按“能干什么 → 怎么跑 → 怎么改”的顺序拆开讲。

2. 技术栈与目录结构:先看清这套示例的骨架

2.1 技术选型:为什么是 C# + COM 互操作

这套示例的核心技术路线是 C# 通过 COM 互操作调用三维地球客户端暴露的自动化接口。三维地球客户端本身是一个原生桌面程序,对外提供了一套基于 COM 的自动化对象模型,C# 通过添加引用或动态调用就能拿到场景对象、图层树、相机控制器这些入口。

常见做法是在项目里引用对应的互操作程序集,或者用Type.GetTypeFromProgID动态创建。示例代码里两种方式都有体现,前者适合强类型开发,后者适合不想绑定具体版本的场景。

// 方式一:强类型引用互操作程序集 using TerraExplorerX; // 互操作命名空间 // 方式二:动态创建,避免版本绑定 Type t = Type.GetTypeFromProgID("TerraExplorer.Application"); dynamic sg = Activator.CreateInstance(t);

逻辑说明:方式一在编译期就能获得智能提示,适合团队协作;方式二在运行时才解析,适合需要兼容多个客户端版本的场景。参数上,ProgID 字符串必须和客户端注册的一致,写错会直接抛COMException。

2.2 目录结构:示例工程里都有什么

一份典型的二次开发示例代码,目录大致长这样:

TerraExplorerDemo/ ├── Properties/ │ └── AssemblyInfo.cs ├── References/ │ └── Interop.TerraExplorerX.dll ├── Forms/ │ ├── MainForm.cs // 主窗口,承载三维控件 │ └── MainForm.Designer.cs ├── Services/ │ ├── SceneService.cs // 场景加载与保存 │ ├── LayerService.cs // 图层增删改查 │ ├── CameraService.cs // 相机飞行与定位 │ └── ObjectService.cs // 三维对象创建与管理 ├── Helpers/ │ ├── ComHelper.cs // COM 对象释放与异常封装 │ └── CoordHelper.cs // 坐标转换工具 ├── Program.cs └── App.config

这个结构把“场景、图层、相机、对象”四类操作拆成独立服务类,主窗口只负责 UI 和事件绑定。好处是二次开发时想改哪块就改哪块,不用在一个几千行的 Form 里翻来翻去。

2.3 核心对象模型:从 Application 到具体对象

三维地球客户端的自动化对象模型是树状的。顶层是 Application,往下是 Project、Scene、Layer、Object。示例代码里最常用的几个入口:

对象作用典型方法
Application客户端进程入口OpenProject、SaveProject
Scene三维场景FlyTo、GetCamera
Layer图层AddLayer、RemoveLayer
Object三维对象CreatePoint、CreatePolyline

理解这棵树之后,写代码就是“拿到入口、逐层往下、调方法”。示例里每个服务类都对应树上的一个节点,职责清晰。

3. 跑起来:从环境准备到第一个三维弹窗

3.1 环境准备:客户端、SDK 与开发工具

要跑通这套示例,本机需要装三维地球客户端,并且客户端版本要和示例引用的互操作程序集匹配。开发工具用 Visual Studio,目标框架建议 .NET Framework 4.7.2 以上,因为 COM 互操作在 .NET Core 上虽然能用,但配置起来更折腾。

常见做法是先在客户端里手动打开一个工程,确认能正常显示三维场景,再跑示例代码。如果客户端本身都起不来,代码再对也没用。

# 检查客户端是否注册了 COM 组件 # 在 PowerShell 里执行 New-Object -ComObject TerraExplorer.Application

如果这条命令不报错,说明 COM 注册正常。报Retrieving the COM class factory错误,通常是客户端没装好或者位数不匹配(32 位 vs 64 位)。

3.2 第一个可运行示例:加载工程并飞到指定坐标

下面这段代码是示例里最基础的入口,作用是启动客户端、打开一个工程、把相机飞到指定经纬度和高度。

using System; using TerraExplorerX; namespace TerraExplorerDemo { class Program { static void Main(string[] args) { // 创建客户端实例 var sg = new SGWorld(); // 打开已有工程,路径按实际修改 sg.Project.Open(@"C:\Projects\demo.fly"); // 飞到指定坐标:经度116.39,纬度39.9,高度5000米 sg.Navigate.FlyTo( sg.Creator.CreatePosition( 116.39, 39.9, 5000, AltitudeTypeCode.ATC_TERRAIN_RELATIVE, 0, 0, 0, 0 ), ActionCode.AC_FLY ); Console.WriteLine("已飞到目标位置"); Console.ReadLine(); } } }

逻辑说明:SGWorld是顶层入口,Project.Open加载工程文件,Creator.CreatePosition构造一个位置对象,Navigate.FlyTo执行飞行。参数里AltitudeTypeCode.ATC_TERRAIN_RELATIVE表示高度相对于地形,ActionCode.AC_FLY表示平滑飞行而不是瞬移。

参数怎么改:经纬度换成目标点,高度单位是米,飞行速度可以通过Navigate.FlyTo的重载或者设置Navigate.Speed调整。如果飞行没反应,先检查工程是否加载成功,再看坐标是否在工程范围内。

3.3 创建三维对象:点、线、面的最小闭环

跑通飞行之后,下一步是在场景里创建对象。示例里的ObjectService封装了创建点、线、面的方法。

// 创建一个点对象 var point = sg.Creator.CreatePoint( 116.39, 39.9, 100, // 经纬度和高度 sg.Creator.CreateColor(255, 0, 0, 128), // 红色半透明 5, // 点大小 "myPoint", // 对象ID "示例点", // 显示名称 "" // 描述 ); // 创建一条折线 var polyline = sg.Creator.CreatePolyline( sg.Creator.CreateGeometryFromArray( new double[] { 116.39, 39.9, 100, 116.40, 39.91, 100 } ), sg.Creator.CreateColor(0, 255, 0, 200), 3, // 线宽 "myLine", "示例线", "" );

逻辑说明:CreatePoint返回一个对象引用,后续可以通过这个引用修改属性或删除。CreateGeometryFromArray接收一个扁平数组,每三个数一组表示一个点的经纬度和高度。颜色用 RGBA 四通道,最后一个参数是透明度。

参数怎么改:点大小、线宽、颜色都是直接改数值。对象 ID 要唯一,重复会覆盖或者报错。如果对象创建了但看不见,检查高度是否在地形以下,或者图层是否被隐藏。

3.4 事件响应:点击对象弹信息框

三维场景里点击对象弹框是最常见的交互。示例里通过绑定OnLButtonClick事件实现。

// 绑定鼠标左键点击事件 sg.OnLButtonClick += (objectId, x, y) => { if (!string.IsNullOrEmpty(objectId)) { // 根据对象ID获取对象信息 var obj = sg.ProjectTree.GetObject(objectId); string name = obj.Name; // 弹出信息框 sg.Window.ShowMessage( $"点击了对象:{name}\nID:{objectId}", "对象信息", MessageBoxButton.OK, MessageBoxIcon.Information ); } };

逻辑说明:事件回调里拿到objectId,通过ProjectTree.GetObject反查对象,再取属性。ShowMessage是客户端封装的弹窗方法,也可以用 WinForms 的MessageBox替代。

参数怎么改:事件类型可以换成OnRButtonClick、OnMouseMove等。弹窗内容可以拼接更多属性,比如坐标、图层名。如果事件不触发,检查是否在创建对象之后才绑定事件,以及对象是否设置了可拾取属性。

4. 避坑与排查:那些第一次跑必踩的坑

4.1 坑一:COM 对象没释放,客户端进程残留

现象:程序关闭后,任务管理器里还有三维地球客户端的进程,再次运行时报“对象被占用”。

原因:COM 对象没有显式释放,.NET 的垃圾回收不会立即回收 COM 引用。

解决:在finally块里调用Marshal.ReleaseComObject,或者用using模式封装。示例里的ComHelper就是干这个的。

try { var sg = new SGWorld(); // ... 业务代码 } finally { if (sg != null) Marshal.ReleaseComObject(sg); }

4.2 坑二:32 位与 64 位不匹配

现象:编译通过,运行时报BadImageFormatException或Class not registered。

原因:客户端是 32 位,但项目编译成了 64 位,或者反过来。

解决:在项目属性里把目标平台改成和客户端一致。常见做法是先用Task Manager看客户端进程有没有*32后缀,有就是 32 位。

4.3 坑三:工程路径含中文或空格导致加载失败

现象:Project.Open返回失败,但路径确实存在。

原因:部分版本的 COM 接口对非 ASCII 路径支持不好。

解决:把工程文件放到纯英文无空格的路径下,或者先用短路径转换。

// 获取短路径 [DllImport("kernel32.dll", CharSet = CharSet.Auto)] static extern int GetShortPathName(string longPath, StringBuilder shortPath, int bufferSize);

4.4 坑四:事件绑定后不触发

现象:代码里绑定了点击事件,但点击对象没反应。

原因:事件绑定时机不对,或者对象没有设置可拾取。

解决:确保在对象创建之后绑定事件,并且创建对象时设置了正确的拾取属性。有些版本需要在ProjectTree里刷新一下。

4.5 坑五:坐标系统不匹配导致对象偏移

现象:创建的点不在预期位置,偏移几百米甚至几公里。

原因:工程用的坐标系和传入的经纬度不匹配,比如工程是投影坐标系,代码传的是地理坐标系。

解决:先用sg.CoordSys查询工程坐标系,必要时做转换。示例里的CoordHelper提供了常见转换方法。

5. 二次开发改造:从示例到生产项目的几个关键动作

5.1 把服务类抽成接口,方便替换和测试

示例里的SceneService、LayerService都是具体类,直接依赖 COM 对象。生产项目里建议抽成接口,COM 实现只做一层薄封装。

public interface ISceneService { void LoadProject(string path); void FlyTo(double lon, double lat, double height); void SaveProject(); } public class ComSceneService : ISceneService { private readonly SGWorld _sg; public ComSceneService(SGWorld sg) { _sg = sg; } public void LoadProject(string path) { _sg.Project.Open(path); } public void FlyTo(double lon, double lat, double height) { _sg.Navigate.FlyTo( _sg.Creator.CreatePosition(lon, lat, height, AltitudeTypeCode.ATC_TERRAIN_RELATIVE, 0, 0, 0, 0), ActionCode.AC_FLY ); } public void SaveProject() { _sg.Project.Save(); } }

这样做的价值:单元测试可以注入一个假的ISceneService,不用启动客户端就能测业务逻辑。换客户端版本时,只需要改ComSceneService一个类。

5.2 用配置文件管理工程路径和常用坐标

示例里路径和坐标是硬编码的,生产项目里应该放到配置文件。

<!-- App.config --> <appSettings> <add key="ProjectPath" value="C:\Projects\demo.fly"/> <add key="HomeLon" value="116.39"/> <add key="HomeLat" value="39.9"/> <add key="HomeHeight" value="5000"/> </appSettings>

读取时用ConfigurationManager.AppSettings["ProjectPath"]。好处是换现场不用重新编译,改配置文件就行。

5.3 异常处理:把 COM 异常翻译成业务异常

COM 调用失败时抛的是COMException,错误码是一串数字,直接抛给用户看不懂。建议在服务层捕获并翻译。

public void LoadProject(string path) { try { _sg.Project.Open(path); } catch (COMException ex) { // 常见错误码翻译 switch (ex.ErrorCode) { case -2147221164: // 类未注册 throw new InvalidOperationException("三维客户端未正确安装或未注册", ex); case -2147024894: // 文件未找到 throw new FileNotFoundException($"工程文件不存在:{path}", ex); default: throw new InvalidOperationException($"加载工程失败,错误码:{ex.ErrorCode}", ex); } } }

这样上层调用者只需要处理InvalidOperationException和FileNotFoundException,不用关心 COM 细节。

5.4 验证改造是否成功的三个检查点

改完之后怎么确认没改坏?我一般走这三步:

第一,跑一遍示例里的基础流程:加载工程、飞行、创建对象、点击弹框,确认主链路通。第二,把配置文件里的路径改成一个不存在的路径,确认异常提示是“工程文件不存在”而不是一串错误码。第三,用单元测试注入假的服务实现,确认业务逻辑不依赖客户端也能跑。

[Test] public void FlyTo_ShouldCallNavigate() { var mock = new Mock<ISceneService>(); var controller = new SceneController(mock.Object); controller.GoHome(); mock.Verify(s => s.FlyTo(116.39, 39.9, 5000), Times.Once); }

这个测试不需要启动三维客户端,跑起来很快,适合放在 CI 里。

从那以后我每次接三维二次开发的项目,都强制先把示例代码跑通一遍,再动手改。因为 COM 互操作这东西,环境问题比代码问题多得多,示例跑通了说明环境没问题,后面出问题就能定位到代码上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询