简介这份资源是面向GIS开发者的TerraExplorer二次开发C#示例代码包基于Skyline平台适合已掌握C#基础语法与面向对象编程、希望快速上手三维地球可视化定制的开发者。内容围绕TerraExplorer SDK展开涵盖事件驱动编程、地图对象与控件操作、Shapefile与KML等数据集成、3D模型加载、空间查询与缓冲区分析、自定义用户界面、WMS/WFS网络服务调用以及多线程与调试优化等核心知识点可帮助读者理解如何用C#构建和控制三维地图场景。压缩包共21个文件约42KB以cs源码文件为主辅以resx资源文件、csproj工程文件与sln解决方案并附带shp、shx、dbf、prj等Shapefile样例数据及fly飞行文件便于直接编译运行与调试。目前已有483人学习下载适合作为TerraExplorer定制开发的入门参考与代码模板。1. 拿到 TerraExplorer 二次开发 C# 示例代码先别急着编译如果你手头正好有一套 Skyline 的 TerraExplorer 环境又需要在 C# 里把三维地球嵌进自己的业务系统那这份 TerraExplorer 二次开发 C# 示例代码就是那种「拿到就能少走两周弯路」的东西。它解决的不是「Skyline 是什么」这种科普问题而是把IApplication、IProject、IPlane、IObjectManager这些 COM 接口怎么在 C# 里安全地 new 出来、怎么挂事件、怎么在 WinForm 里塞进一个三维窗口用可运行的代码摆在你面前。适合两类人一类是刚接手 Skyline 项目、被COMException和0x80040154折腾到怀疑人生的新手另一类是想把 TerraExplorer 当渲染内核、外面套自己业务逻辑的熟手。这份示例代码的价值在于它把官方文档里散落在各处的接口调用串成了一条能跑通的链路而不是让你对着 IDL 猜参数。2. 环境搭建与工程结构从 COM 引用到第一个能转的地球2.1 为什么必须用 x86 或 x64 对齐而不是 AnyCPUTerraExplorer 的二次开发本质是 COM 互操作TerraExplorerX.dll这类组件是分位数的。你如果拿一个 AnyCPU 的控制台程序去引用它在 64 位系统上默认跑成 64 位进程但注册表里注册的可能是 32 位组件结果就是经典的Retrieving the COM class factory for component with CLSID {...} failed due to the following error: 80040154 未注册类。这不是代码写错了是位数没对齐。常见做法是先确认你装的 TerraExplorer 是 32 位还是 64 位然后在 Visual Studio 里把目标平台显式设成对应的x86或x64不要留Any CPU。我一般会在.csproj里直接写死PropertyGroup !-- 必须与已安装的 TerraExplorer 组件位数一致否则 80040154 -- PlatformTargetx86/PlatformTarget Prefer32Bitfalse/Prefer32Bit /PropertyGroup参数说明PlatformTarget决定编译产物是 32 位还是 64 位Prefer32Bit只在 AnyCPU 下有意义这里显式关掉避免干扰。改完记得清理bin、obj再重新生成否则旧的 AnyCPU 产物可能还在。2.2 引用与初始化把 IApplication 真正拿到手示例代码里最核心的一步是把 TerraExplorer 的 COM 对象实例化出来。很多人卡在new TerraExplorerClass()直接抛异常原因通常是组件没注册或者当前用户没有权限访问该 COM 组件。正确的初始化顺序是先Activator.CreateInstance再拿IApplication接口最后才是IProject。using TerraExplorerX; // 引用 TerraExplorerX.dll 后生成的互操作程序集 // 创建 TerraExplorer 应用实例 IApplication app new TerraExplorerClass(); // 通过 IApplication 拿到当前项目对象 IProject project app as IProject; if (project null) { throw new InvalidOperationException(无法获取 IProject检查组件版本是否匹配); } // 打开一个本地 fly 文件路径按实际替换 project.Open(C:\Data\demo.fly, 0);逻辑说明TerraExplorerClass是 COM 组件的托管包装as IProject这一步是把应用对象转成项目接口TerraExplorer 的很多功能都挂在IProject上而不是IApplication上。Open的第二个参数是打开标志示例里传 0 表示默认方式实际项目里如果要控制是否显示加载进度、是否只读需要查对应枚举值。参数说明demo.fly是 Skyline 的项目文件格式里面记录了图层、地形、路径等配置路径必须用绝对路径相对路径在 COM 调用里经常解析失败。2.3 在 WinForm 里嵌入三维窗口的正确姿势示例代码里通常会有一个AxTerraExplorerX的 ActiveX 控件这是把三维视图嵌进 WinForm 最省事的方式。但要注意这个控件和IApplication是两套东西控件负责显示IApplication负责逻辑控制。两者要通过AxTerraExplorerX.IApplication属性关联起来。// 假设窗体上已经拖了一个 axTE 控件 private void InitTE() { // 把控件里的应用对象赋给成员变量 this.teApp this.axTE.IApplication; this.teProject this.teApp as IProject; // 加载项目文件 this.teProject.Open(C:\Data\demo.fly, 0); // 设置三维窗口的初始视角经度、纬度、高度、朝向、俯仰、翻滚 this.teProject.SetCamera(116.39, 39.9, 5000, 0, -45, 0, 0); }逻辑说明axTE.IApplication返回的就是控件内部已经初始化好的应用对象比你自己new TerraExplorerClass()更稳因为它和显示控件是同一个实例。SetCamera的七个参数依次是经度、纬度、高度米、方位角、俯仰角、翻滚角、以及一个速度/延迟参数示例里传 0 表示立即跳转。这里最容易翻车的是经纬度顺序Skyline 用的是经度在前、纬度在后和某些地图库相反写反了地球会转到奇怪的位置。3. 对象操作与事件绑定让地球上的东西听你的话3.1 用 IObjectManager 创建和管理三维对象TerraExplorer 里所有看得见的东西——标注、模型、多边形、路径——都通过IObjectManager来创建。示例代码里一般会演示如何创建一个文字标注和一个模型。关键是要理解每个对象都有一个ID后续的移动、删除、属性修改都靠这个 ID。IObjectManager objMgr teProject.ObjectManager; // 创建一个文字标注返回对象 ID int labelId objMgr.CreateText( 监测点A, // 显示文字 116.40, 39.91, 100, // 经度、纬度、高度 0, // 样式标志0 为默认 , // 附加信息 -1 // 分组 ID-1 表示不分组 ); // 创建一个模型对象指向本地 xpl 模型文件 int modelId objMgr.CreateModel( C:\Data\models\tower.xpl, 116.41, 39.92, 50, 0, 0, 0, // 旋转角度 1.0, // 缩放 -1 ); // 后续可以通过 ID 修改位置 objMgr.SetPosition(labelId, 116.42, 39.93, 120, 0, 0, 0);逻辑说明CreateText和CreateModel都返回int类型的对象 ID这个 ID 在当前项目内唯一。SetPosition用来动态更新位置做实时监控类项目时你会频繁调用它。参数说明高度单位是米模型路径必须是.xpl格式这是 Skyline 自己的模型格式不能直接喂.obj或.fbx需要先用转换工具处理。分组 ID 传 -1 表示不归入任何组如果要做批量显隐控制可以先把对象加到一个组里。3.2 事件绑定处理鼠标点击和对象选中示例代码里比较有价值的部分是事件绑定。TerraExplorer 的 COM 事件在 C# 里需要通过委托来挂直接写objMgr.OnObjectSelected ...是不行的因为 COM 事件接口和 .NET 事件模型不是一回事。常见做法是用ITerraExplorerEvents接口。// 假设 teApp 是 IApplication ITerraExplorerEvents_Event events teApp as ITerraExplorerEvents_Event; if (events ! null) { // 挂载对象选中事件 events.OnObjectSelected new ITerraExplorerEvents_OnObjectSelectedEventHandler(OnObjSelected); } private void OnObjSelected(int objectId) { // objectId 就是被选中对象的 ID可以据此查属性或做联动 Console.WriteLine($选中了对象{objectId}); }逻辑说明ITerraExplorerEvents_Event是互操作程序集生成的事件接口OnObjectSelected是其中一个事件。挂载时要用对应的委托类型不能直接写 lambda 了事因为 COM 事件需要保持委托引用否则可能被 GC 回收导致事件失效。参数说明objectId是选中对象的 ID如果选中的是地球本身或空白区域这个值可能是 0 或 -1业务代码里要做判空处理。3.3 相机飞行与路径漫游的参数控制做演示汇报时经常需要让相机沿着一条路径飞。示例代码里一般会演示ICamera或IProject.SetCamera的连续调用。更顺滑的做法是用IPath对象配合FlyTo。// 创建一个路径对象 int pathId objMgr.CreatePath( 巡检路线, 0, // 路径类型 -1 ); // 向路径中添加节点经度、纬度、高度、速度 objMgr.AddPointToPath(pathId, 116.40, 39.90, 200, 50, 0, 0, 0); objMgr.AddPointToPath(pathId, 116.41, 39.91, 300, 50, 0, 0, 0); objMgr.AddPointToPath(pathId, 116.42, 39.92, 250, 50, 0, 0, 0); // 让相机沿路径飞行 teProject.Navigate(pathId, 0); // 第二个参数为飞行模式逻辑说明CreatePath创建一条空路径AddPointToPath逐个添加节点每个节点包含位置和速度。Navigate触发相机沿路径运动。参数说明速度单位是米/秒示例里给 50 是比较平缓的演示速度飞行模式 0 通常表示按路径顺序飞具体枚举值要对照版本查。这里容易踩的坑是路径节点太少会导致转弯生硬一般建议每段至少 5 到 8 个节点做插值。4. 避坑与排查那些让示例代码跑不起来的常见问题4.1 现象编译通过运行时抛 80040154 未注册类原因目标平台位数和已注册的 TerraExplorer COM 组件不一致或者组件根本没注册。解决先确认安装目录下的TerraExplorerX.dll是 32 位还是 64 位把项目PlatformTarget改成一致如果确认位数没错用管理员权限运行regsvr32重新注册对应的 DLL。注意注册 64 位组件要用 64 位的regsvr32路径在SysWOW64下的是 32 位版本别搞混。4.2 现象project.Open返回失败但文件确实存在原因.fly文件里引用的地形或影像数据路径是绝对路径换机器后那些路径不存在导致打开过程中断。解决用文本编辑器打开.fly文件它本质是 XML检查里面的数据源路径把失效路径改成新机器上的实际路径或者用 TerraExplorer 自带的打包功能把数据一起打包成相对路径。这个坑在把项目从开发机搬到演示机时特别常见。4.3 现象事件挂上了但从来不触发原因委托对象被垃圾回收了或者挂事件的时机太早控件还没初始化完。解决把委托声明成类的成员变量保证生命周期和窗体一致挂事件的代码放到Form_Load或控件OnCreateControl之后不要放在构造函数最前面。另外某些事件需要先设置IApplication的某个开关才会生效具体看版本说明。4.4 现象创建模型后地球上看不到原因模型路径不对、模型格式不是.xpl、或者高度设成了 0 导致模型埋在地形下面。解决先用 TerraExplorer 客户端手动加载同一个模型文件确认能显示然后把高度调大一点比如 50 到 100 米排除地形遮挡最后检查CreateModel的缩放参数有些模型导出时单位是厘米缩放要设成 0.01 才能正常显示。4.5 现象程序退出后 TerraExplorer 进程残留原因COM 对象没有正确释放IApplication还活着。解决在窗体关闭事件里显式调用Marshal.ReleaseComObject释放IProject、IObjectManager、IApplication并把变量置为 null。顺序很重要先释放子对象再释放父对象否则可能抛异常。如果还残留检查是不是有事件没解绑事件持有引用也会阻止释放。5. 进阶技巧用配置文件驱动对象创建少改代码多干活示例代码跑通之后真正的项目里不会把经纬度硬编码在 C# 里。我一般会抽一层配置用 JSON 或 XML 描述要创建哪些对象、位置在哪、绑什么事件。这样现场调试时只改配置不改代码重新编译都省了。下面是一个简化版的 JSON 配置和对应的解析代码。{ objects: [ { type: text, name: 入口, lon: 116.40, lat: 39.90, height: 100, style: 0 }, { type: model, name: 设备A, path: C:\\Data\\models\\device.xpl, lon: 116.41, lat: 39.91, height: 50, scale: 1.0 } ] }// 用 Newtonsoft.Json 或 System.Text.Json 反序列化 public class TeObjectConfig { public string Type { get; set; } public string Name { get; set; } public string Path { get; set; } public double Lon { get; set; } public double Lat { get; set; } public double Height { get; set; } public int Style { get; set; } public double Scale { get; set; } 1.0; } public void LoadFromConfig(string jsonPath, IObjectManager objMgr) { var json File.ReadAllText(jsonPath); var config JsonConvert.DeserializeObjectTeObjectConfigList(json); foreach (var item in config.Objects) { switch (item.Type) { case text: objMgr.CreateText(item.Name, item.Lon, item.Lat, item.Height, item.Style, , -1); break; case model: objMgr.CreateModel(item.Path, item.Lon, item.Lat, item.Height, 0, 0, 0, item.Scale, -1); break; default: // 未知类型记日志不中断整体加载 Console.WriteLine($未知对象类型{item.Type}); break; } } }逻辑说明配置类TeObjectConfig的字段和 JSON 键一一对应Scale给了默认值 1.0 避免漏填导致模型消失。LoadFromConfig里用switch分发不同类型遇到未知类型只记日志不抛异常保证一个配置写错不会让整个场景加载失败。参数说明jsonPath用绝对路径objMgr从teProject.ObjectManager传入。这套做法在需要频繁调整演示场景的项目里特别省事现场改完 JSON 重启程序即可不用重新编译。验证配置是否生效我习惯在加载完成后遍历一遍objMgr里的对象数量和配置文件里的条目数对一下。如果数量对不上多半是某条配置的经纬度超出了当前地形范围或者模型路径里有中文导致 COM 调用失败。从那以后我每次拿到新的示例代码都强制先跑一遍最小配置——一个文字标注加一个模型——确认环境通了再往上堆业务逻辑这样出问题时排查范围小得多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?