资讯动态

TerraExplorer 二次开发 C# 示例代码:从加载三维地球到叠加业务图层

发布时间:2026/9/29 1:23:09 来源:尧图企业网站定制
简介这份资源是面向GIS与三维可视化开发者的TerraExplorer二次开发C#示例代码包基于Skyline平台适合已掌握C#基础语法与面向对象编程、希望快速上手TerraExplorer SDK的中级开发者。内容围绕事件驱动编程、地图对象与控件操作、Shapefile与KML等数据集成、3D模型加载、空间查询与缓冲区分析、自定义界面及多线程优化等核心知识点展开可帮助读者理解如何用C#调用API构建定制化三维地球应用。压缩包共21个文件约42KB以cs源码文件为主辅以resx资源文件、csproj与sln工程文件并附带shp、dbf、shx、prj等Shapefile样例数据及fly飞行文件便于直接编译运行与调试。目前已有482人学习下载读者可借助完整工程结构与示例代码快速掌握TerraExplorer二次开发的关键流程与排错思路。1. TerraExplorer 二次开发 C# 示例代码从加载三维地球到叠加业务图层的完整路径很多做 GIS 上位机或三维可视化的同行第一次拿到 TerraExplorer 的 C# 示例代码时往往卡在同一个地方窗体里那个三维地球控件能显示但不知道怎么把外部数据、业务图层、飞行视角和交互事件串起来。TerraExplorer 本身是一套成熟的三维地球引擎它的二次开发接口COM 组件允许用 C# 调用把三维场景嵌入到自己的 WinForms 或 WPF 程序里。这个标题要解决的核心问题就是如何用 C# 写出一套能跑通、能扩展、能对接业务数据的 TerraExplorer 二次开发示例代码。适合有 C# 基础、做过上位机或桌面端开发、需要把三维地图集成进业务系统的工程师。下面从环境搭建、核心对象模型、图层操作、事件交互到避坑排查一步步拆开讲。2. 环境准备与项目骨架把 TerraExplorer 控件嵌进 C# 窗体2.1 安装与引用COM 组件的正确引入方式TerraExplorer 的二次开发依赖本机安装的 TerraExplorer Pro 或 TerraExplorer Plus 运行时。安装完成后在 Visual Studio 里新建一个 Windows Forms 项目目标框架建议选 .NET Framework 4.7.2 或以上因为 COM 互操作在 .NET Core 下的支持并不完整踩过这个坑的人不少。在解决方案资源管理器中右键“引用” → “添加引用” → “COM”找到TerraExplorerX Control和TerraExplorerX Object Library勾选后确定。VS 会自动生成互操作程序集Interop.TerraExplorerX.dll。如果列表里找不到说明运行时没装好或者需要以管理员身份运行 VS 再试一次。// 在窗体代码顶部引入命名空间 using TerraExplorerX; // 互操作程序集生成的命名空间 public partial class MainForm : Form { private SGWorld70 _sgWorld; // 全局三维世界对象版本号以实际引用为准 public MainForm() { InitializeComponent(); } private void MainForm_Load(object sender, EventArgs e) { // 从窗体上的 TerraExplorer 控件获取 SGWorld 对象 // axTE 是拖到窗体上的 TerraExplorerX 控件实例名 _sgWorld axTE.CreateInstance(TerraExplorer.SGWorld); if (_sgWorld null) { MessageBox.Show(TerraExplorer 初始化失败请检查运行时是否安装。); return; } // 加载一个本地 fly 工程文件也可以传空字符串创建空白场景 _sgWorld.Open(C:\Projects\Demo\demo.fly, ); } }这段代码的逻辑是窗体加载时通过控件实例创建SGWorld对象然后打开一个.fly工程文件。SGWorld是整个二次开发的总入口后面所有图层、相机、信息树的操作都从它出发。参数说明Open方法的第一个参数是工程文件路径第二个参数是密码没有密码传空字符串。如果传空路径会创建一个空白三维场景适合从零开始叠加数据。提示SGWorld70中的版本号取决于你安装的 TerraExplorer 版本常见的有SGWorld60、SGWorld65、SGWorld70。不要硬编码可以在引用后查看互操作程序集里的实际类型名。2.2 项目骨架把三维控件和业务面板分开一个可维护的 TerraExplorer 二次开发项目不建议把所有逻辑塞进主窗体。我一般会拆成三层三维控件层负责 SGWorld 生命周期、业务图层层负责创建和管理图层、交互事件层负责响应鼠标和菜单。这样后面加数据源、换工程文件时不会牵一发动全身。// 三维控件封装类负责 SGWorld 的创建和释放 public class TeControlWrapper : IDisposable { private SGWorld70 _sgWorld; private bool _disposed false; public SGWorld70 World _sgWorld; public bool Initialize(AxTerraExplorerX.AxTE axTE, string flyPath) { _sgWorld axTE.CreateInstance(TerraExplorer.SGWorld) as SGWorld70; if (_sgWorld null) return false; _sgWorld.Open(flyPath, ); return true; } public void Dispose() { if (_disposed) return; // 释放 COM 对象避免进程残留 if (_sgWorld ! null) { Marshal.ReleaseComObject(_sgWorld); _sgWorld null; } _disposed true; } }这里用IDisposable模式管理 COM 对象的释放是血泪经验如果不手动释放关闭窗体后进程里还会残留 TerraExplorer 的宿主进程任务管理器里能看到反复调试几次就卡死了。参数说明Marshal.ReleaseComObject是 .NET 里释放 COM 引用的标准做法调用后要把引用置空否则 GC 不会回收。3. 核心对象模型与图层操作用 C# 创建点线面和信息树3.1 SGWorld 下的五大核心对象TerraExplorer 的二次开发对象模型不算复杂但第一次看官方文档容易迷路。我把它归纳成五个核心对象理解了这五个大部分示例代码都能看懂对象作用典型方法SGWorld总入口管理工程和全局设置Open、Close、NavigateCreator创建几何对象点、线、面、文字CreatePoint、CreatePolyline、CreatePolygonProjectTree信息树管理图层分组和可见性GetGroup、CreateGroupCamera相机控制飞行和视角定位FlyTo、SetPositionCoordServices坐标转换屏幕坐标与地理坐标互转GetCoordFromScreen这五个对象都从SGWorld直接或间接获取。比如Creator通过_sgWorld.Creator拿到ProjectTree通过_sgWorld.ProjectTree拿到。很多新手示例代码里直接写sgWorld.Creator.CreatePoint(...)但没讲清楚这些对象之间的关系导致换一个版本就找不到方法。3.2 创建点线面从经纬度到三维几何体下面这段代码演示如何在指定经纬度创建一个点对象并把它挂到信息树的某个分组下。这是 TerraExplorer 二次开发里最常用的操作业务系统里的设备点位、车辆轨迹、区域边界都靠它。// 创建一个点对象并添加到信息树 public void CreatePointDemo(SGWorld70 sgWorld, double lon, double lat, double alt) { // 获取创建器 ICreator70 creator sgWorld.Creator; // 创建几何对象参数依次为几何类型、坐标、描述、分组ID // 坐标格式为 WGS84 经纬度高度单位米 IFeature70 point creator.CreatePoint( lon, // 经度 lat, // 纬度 alt, // 高度 设备点位A, // 对象名称显示在信息树 这是一个示例点位, // 描述 0, // 对象类型0 表示默认 0 // 分组ID0 表示根分组 ); if (point ! null) { // 设置点位的显示样式比如图标和颜色 point.Style sgWorld.Creator.CreateImageStyle( C:\Icons\device.png, // 图标路径 0, 0, // 图标偏移 16, 16 // 图标宽高 ); } }逻辑说明CreatePoint返回一个IFeature70接口代表创建出来的几何对象。拿到这个对象后可以继续设置样式、绑定属性、添加点击事件。参数说明经纬度用 WGS84 坐标系高度是海拔米数分组ID 传0表示挂在根节点如果要挂到自定义分组需要先用ProjectTree.CreateGroup创建分组并拿到分组ID。线对象和面对象的创建方式类似只是坐标从单个点变成点数组// 创建一条折线模拟车辆轨迹 public void CreatePolylineDemo(SGWorld70 sgWorld, double[] lons, double[] lats) { // 构造坐标数组格式为 lon,lat,alt 的字符串数组 string[] coords new string[lons.Length]; for (int i 0; i lons.Length; i) { coords[i] ${lons[i]},{lats[i]},0; } // 创建折线对象 IFeature70 line sgWorld.Creator.CreatePolyline( coords, // 坐标数组 0xFFFF0000, // 颜色ARGB 格式这里为红色 3, // 线宽像素 车辆轨迹, // 名称 轨迹描述, // 描述 0 // 分组ID ); }参数说明颜色用 ARGB 十六进制0xFFFF0000是红色0xFF00FF00是绿色。线宽单位是像素不是地理单位。坐标数组里每个元素是经度,纬度,高度的字符串高度传 0 表示贴地。3.3 信息树分组管理让图层可开关、可分类业务系统里图层多了以后信息树会变得很乱。我一般会按业务类型创建分组比如“设备层”“轨迹层”“区域层”每个分组可以单独控制可见性。// 创建分组并返回分组ID public string CreateGroupDemo(SGWorld70 sgWorld, string groupName) { IProjectTree70 tree sgWorld.ProjectTree; // 在根节点下创建分组 // 参数分组名称、父分组ID0 为根、分组类型 string groupId tree.CreateGroup( groupName, // 分组名称 0, // 父分组ID 0 // 分组类型0 表示普通分组 ); return groupId; } // 控制分组可见性 public void SetGroupVisible(SGWorld70 sgWorld, string groupId, bool visible) { IProjectTree70 tree sgWorld.ProjectTree; // 获取分组对象并设置可见性 ITerrainObject70 group tree.GetGroup(groupId) as ITerrainObject70; if (group ! null) { group.Visibility visible ? 1 : 0; } }逻辑说明CreateGroup返回的groupId是一个字符串后续创建几何对象时把这个 ID 传进去对象就会挂到该分组下。SetGroupVisible通过分组ID拿到分组对象设置Visibility属性控制显示隐藏。参数说明Visibility是整数1 表示可见0 表示隐藏。注意分组ID 在工程保存后可能会变化不要把它硬编码到配置文件里。我一般会在程序启动时重新创建分组或者用分组名称去查找。4. 相机控制与事件交互让三维场景跟着业务逻辑走4.1 相机飞行定位到指定坐标和视角三维场景如果不能让相机飞过去业务人员用起来会很别扭。TerraExplorer 的相机控制通过Camera对象完成支持飞到指定点、设置俯仰角和偏航角。// 相机飞到指定位置 public void FlyToPosition(SGWorld70 sgWorld, double lon, double lat, double alt, double pitch, double yaw, double duration) { ICamera70 camera sgWorld.Camera; // 构造目标位置 // 参数经度、纬度、高度、俯仰角、偏航角、飞行时间秒 camera.FlyTo( lon, // 目标经度 lat, // 目标纬度 alt, // 目标高度米 pitch, // 俯仰角-90 到 0-90 为垂直向下 yaw, // 偏航角0 到 3600 为正北 duration // 飞行时间秒0 表示瞬间跳转 ); }参数说明俯仰角pitch的范围是 -90 到 0-90 表示垂直向下看0 表示水平看。偏航角yaw是 0 到 3600 为正北90 为正东。飞行时间duration控制动画速度设 0 会瞬间跳过去设 2 到 3 秒比较自然。4.2 鼠标事件点击三维对象触发业务逻辑TerraExplorer 控件支持鼠标事件可以在 C# 里订阅OnLButtonClick等事件拿到点击位置的屏幕坐标再通过CoordServices转换成地理坐标或者直接判断点到了哪个对象。// 订阅鼠标点击事件 private void AxTE_OnLButtonClick(object sender, _ITerraExplorerEvents70_OnLButtonClickEvent e) { // e.X 和 e.Y 是屏幕坐标 // 通过 CoordServices 获取点击位置的地理坐标 ICoordServices70 coordServices _sgWorld.CoordServices; // 将屏幕坐标转换为地理坐标 // 参数屏幕X、屏幕Y、返回的经度、返回的纬度、返回的高度 double lon, lat, alt; coordServices.GetCoordFromScreen(e.X, e.Y, out lon, out lat, out alt); // 在点击位置创建一个临时标记 _sgWorld.Creator.CreatePoint(lon, lat, alt, 点击位置, , 0, 0); // 也可以判断是否点到了某个对象 // 通过 GetObjectFromScreen 获取屏幕位置的对象 object obj _sgWorld.GetObjectFromScreen(e.X, e.Y); if (obj is IFeature70 feature) { // 点到对象了可以弹出属性窗口或执行其他业务逻辑 MessageBox.Show($点击了对象{feature.Name}); } }逻辑说明OnLButtonClick事件里拿到的是屏幕像素坐标GetCoordFromScreen把它转成经纬度和高度。GetObjectFromScreen返回屏幕位置对应的三维对象如果点到了已创建的点线面会返回对应的IFeature70。参数说明GetCoordFromScreen的输出参数是out类型调用前不需要初始化。提示鼠标事件在 TerraExplorer 控件上的注册方式取决于你用的是 Ax 控件还是 WPF 宿主。Ax 控件可以直接在属性面板里双击事件生成处理函数WPF 宿主需要手动挂接事件。4.3 属性绑定把业务数据挂到三维对象上三维对象创建后往往需要绑定业务属性比如设备编号、状态、更新时间。TerraExplorer 支持通过FeatureAttributes给对象添加自定义属性点击对象时可以读取这些属性。// 给对象添加自定义属性 public void SetFeatureAttributes(IFeature70 feature, Dictionarystring, string attributes) { // 获取属性集合 IFeatureAttributes70 attrs feature.FeatureAttributes; foreach (var kv in attributes) { // 添加属性参数属性名、属性值 attrs.Add(kv.Key, kv.Value); } } // 读取对象属性 public string GetFeatureAttribute(IFeature70 feature, string key) { IFeatureAttributes70 attrs feature.FeatureAttributes; // 通过属性名获取值 return attrs.GetAttribute(key) as string; }逻辑说明FeatureAttributes是一个键值对集合可以动态添加和读取。业务系统里可以把设备状态、告警信息写进去点击对象时读取并显示在属性面板里。参数说明属性名和属性值都是字符串复杂对象可以序列化成 JSON 再存。5. 避坑与排查TerraExplorer 二次开发中常见的五个翻车点5.1 现象窗体关闭后进程残留再次运行报“无法创建 COM 对象”原因SGWorld对象没有释放COM 引用计数不为零宿主进程一直挂着。解决在窗体FormClosing事件里调用Marshal.ReleaseComObject(_sgWorld)并把引用置空。如果用了多个 COM 对象Creator、Camera 等也要一并释放。我一般会写一个Dispose方法统一处理。5.2 现象创建的点线面不显示信息树里也没有原因分组ID传错了或者坐标格式不对。TerraExplorer 的坐标必须是 WGS84 经纬度如果传了投影坐标比如 Web Mercator 的米单位对象会跑到地球外面。解决确认坐标是经纬度分组ID用0先测试根节点。如果还是不显示检查CreatePoint的返回值是否为 nullnull 说明创建失败通常是参数类型不对。5.3 现象鼠标点击事件不触发或者触发了但坐标转换结果偏差很大原因事件没有正确挂接或者CoordServices没有初始化。解决Ax 控件的事件要在属性面板里确认已绑定WPF 宿主需要手动axTE.OnLButtonClick ...。坐标偏差大通常是屏幕坐标传错了e.X和e.Y是相对于控件左上角的像素坐标不是窗体坐标。5.4 现象加载 .fly 工程文件时报“文件格式不支持”或“版本不匹配”原因TerraExplorer 运行时版本和工程文件版本不一致。高版本创建的 .fly 文件在低版本运行时打不开。解决确认本机安装的 TerraExplorer 版本用对应版本创建工程文件。如果必须兼容低版本可以在高版本里导出为低版本格式但会丢失部分样式。5.5 现象多线程里调用 SGWorld 方法导致程序崩溃或界面卡死原因COM 对象是单线程套间STA模型不能在子线程里直接调用。解决所有对SGWorld及其子对象的调用都放在 UI 线程里或者用Invoke切回 UI 线程。如果需要在后台线程准备数据先把数据准备好再切到 UI 线程创建三维对象。注意TerraExplorer 的 COM 接口对线程敏感这是最容易翻车的地方。我见过有人在Task.Run里创建点对象程序直接闪退日志里只有AccessViolationException。6. 进阶技巧用 C# 委托和事件封装可复用的三维图层管理器6.1 把图层操作封装成管理器类当项目里需要频繁创建、更新、删除三维对象时直接在每个业务模块里调Creator会很难维护。我一般会写一个TeLayerManager类把常用的图层操作封装成方法并用委托暴露事件让业务模块订阅。// 三维图层管理器封装常用操作 public class TeLayerManager { private SGWorld70 _sgWorld; private Dictionarystring, IFeature70 _features new Dictionarystring, IFeature70(); // 定义对象点击事件的委托 public delegate void FeatureClickedHandler(string featureId, string featureName); public event FeatureClickedHandler FeatureClicked; public TeLayerManager(SGWorld70 sgWorld) { _sgWorld sgWorld; } // 添加点对象并记录到字典 public string AddPoint(string id, double lon, double lat, double alt, string name) { IFeature70 point _sgWorld.Creator.CreatePoint(lon, lat, alt, name, , 0, 0); if (point ! null) { _features[id] point; return id; } return null; } // 更新点对象位置 public void UpdatePointPosition(string id, double lon, double lat, double alt) { if (_features.TryGetValue(id, out IFeature70 point)) { // 通过 Position 属性更新位置 point.Position _sgWorld.Creator.CreatePosition(lon, lat, alt, 0, 0, 0); } } // 删除点对象 public void RemovePoint(string id) { if (_features.TryGetValue(id, out IFeature70 point)) { _sgWorld.ProjectTree.DeleteItem(point.ID); _features.Remove(id); } } // 触发点击事件由外部鼠标事件调用 public void RaiseFeatureClicked(string id, string name) { FeatureClicked?.Invoke(id, name); } }逻辑说明这个管理器用字典维护业务ID和三维对象的映射业务模块只需要操作业务ID不需要直接接触 COM 对象。FeatureClicked事件让业务模块可以订阅点击行为比如弹出设备详情窗口。参数说明CreatePosition用于构造位置对象参数依次是经度、纬度、高度、俯仰角、偏航角、翻滚角。6.2 用委托解耦鼠标事件和业务逻辑鼠标事件里拿到点击的对象后不要直接写业务代码而是通过管理器的事件往外抛。这样业务模块可以独立变化三维控件层不需要知道业务细节。// 在窗体鼠标事件里调用管理器 private void AxTE_OnLButtonClick(object sender, _ITerraExplorerEvents70_OnLButtonClickEvent e) { object obj _sgWorld.GetObjectFromScreen(e.X, e.Y); if (obj is IFeature70 feature) { // 通过管理器触发事件业务模块订阅后处理 _layerManager.RaiseFeatureClicked(feature.ID, feature.Name); } } // 业务模块订阅事件 private void InitBusiness() { _layerManager.FeatureClicked (id, name) { // 这里写业务逻辑比如查询数据库、弹出窗口 MessageBox.Show($设备 {name} 被点击ID{id}); }; }逻辑说明鼠标事件只负责识别对象具体业务逻辑通过委托抛给订阅者。这样换业务场景时不需要改三维控件层的代码。参数说明FeatureClicked是自定义委托参数是对象ID和名称业务模块可以根据ID去查数据库或更新界面。6.3 验证方法用日志和断点确认对象生命周期三维对象的创建和释放不容易肉眼确认我一般会在管理器里加日志记录每次创建、更新、删除的对象ID和时间戳。调试时打开日志文件能快速定位是创建失败还是释放遗漏。// 简单的日志记录 private void Log(string message) { string logPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, te_layer.log); File.AppendAllText(logPath, ${DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}\r\n); } // 在 AddPoint 里调用 Log($AddPoint id{id}, lon{lon}, lat{lat}, alt{alt});参数说明日志文件放在程序目录下每次操作追加一行。如果发现创建后没有对应删除记录说明有对象泄漏需要检查业务代码是否调用了RemovePoint。这套方案我在几个上位机项目里用过三维场景的稳定性和可维护性比直接裸写 COM 调用好很多。最大的教训是不要等到程序崩溃才去管 COM 释放一开始就把管理器类和日志加上后面省下来的排查时间远超写这些代码的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑