尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TerraExplorer C#二次开发实战:COM接口调用与三维场景构建

TerraExplorer C#二次开发实战:COM接口调用与三维场景构建 简介TerraExplorer二次开发C#示例代码是一份面向地理信息系统开发者和Skyline平台用户的实战资源。围绕TerraExplorer开发工具包演示用C#构建三维地球应用的核心流程包括地图控件初始化、矢量与影像数据加载、三维模型放置、事件响应及自定义用户界面等适合已有C#基础、希望快速掌握Skyline定制开发的进阶学习者。压缩包共21个文件体量约42KB以6个C#源码文件为主配有解决方案与工程配置文件同时包含矢量数据shp、场景数据fly等示例素材便于直接打开工程查看数据集成与场景构建方式。目前已有482人浏览学习。示例工程覆盖事件驱动、空间查询、多线程调用等接口用法开发者可对照源码理解对象模型与调用流程有效缩短从零搭建环境的准备与试错周期提升定制开发效率。1. 打开 TerraExplorer 的 C# 大门这套示例代码到底在解决什么问题接到一个数字孪生项目甲方要求把管网、楼栋、摄像头叠加到三维地球上还要能按经纬度飞行定位。三维引擎选型时被推荐了 Skyline 的 TerraExplorer理由很直接它不需要从零写渲染管线地球地形、影像、三维模型都是现成的C# 开发者能通过 COM 接口直接操作场景。听起来很美真正动手才发现TerraExplorer 的 C# 二次开发资料少得可怜官方示例多是 JavaScript 和 C网上能搜到的 C# 示例代码要么是片段要么是老版本接口照着敲完一堆红色波浪线。这篇文章就是来解决这个问题的。我按实际项目里反复验证过的调用链把 TerraExplorer 二次开发中 C# 最常见的功能——初始化 SGWorld、加载数据、创建对象、控制飞行——逐段拆开讲附带能直接编译的示例代码和参数说明。适合刚接到 TerraExplorer 相关开发任务、需要用 C# 上位机思路操作三维场景的工程师。看完你至少能跑通一个最小工程并且知道哪些坑是 COM 接口特有的、哪些是 TerraExplorer 自己的脾气。2. 把 C# 工程和 TerraExplorer 接起来环境配置与最小启动代码2.1 前置条件TerraExplorer 运行库和 C# 工程的引用方式TerraExplorer 的二次开发本质上是调用它的 COM 组件C# 这边通过 Interop 程序集访问。官方安装包通常自带 TerraExplorerX.dll 和对应的 Interop 动态库安装目录一般在C:\Program Files\Skyline\TerraExplorer Pro\下。工程里要做的第一件事就是添加引用右键项目的“引用”选择“添加引用”在 COM 标签页里找到 TerraExplorerX 相关条目。引用添加完之后Visual Studio 会自动生成 Interop.TerraExplorerX.dll这个文件是 C# 和 COM 之间的桥梁。如果 COM 标签页里找不到条目可以手动浏览到 TerraExplorerX.dll 直接添加。还有一种常见做法是用Regsvr32手动注册运行库不过 TerraExplorer 安装时一般会自动注册只有绿色版或手动拷贝运行库时才会用到这步。一个容易忽略的细节是目标平台。TerraExplorer 的运行库可能是 32 位或 64 位C# 工程必须匹配对应位数否则运行时会出现CLSID 未注册或类未找到的错误。我一般直接在工程属性里把“平台目标”设为 x64如果用的是老版本运行库就改成 x86。这个配置不提前做后续所有代码都会卡在初始化这一步。2.2 最小启动代码初始化 SGWorld 并加载三维工程TerraExplorer 的 C# 开发里SGWorld 是所有 API 的入口。它类似于 C# 里的 Application 对象通过它才能拿到工程、对象、导航、地形、分析这五大子系统的接口引用。先写一个最小启动代码using TerraExplorerX; using System; using System.Windows.Forms; public class TerraExplorerApp { private SGWorld70 sgWorld; private ITerraExplorer70 terraExplorer; public bool Initialize() { try { // 创建 SGWorld 对象这是 COM 入口 sgWorld new SGWorld70(); // 初始化并连接到 TerraExplorer 主程序 terraExplorer sgWorld.Initialize( TerraExplorerX.TE_CONNECTION_MODE.TE_CONNECTION_NEW, TerraExplorer, , ); return terraExplorer ! null; } catch (Exception ex) { MessageBox.Show(初始化失败: ex.Message); return false; } } public void LoadProject(string projectPath) { // 若 terraExplorer 为空说明主程序没有启动或初始化失败 if (terraExplorer null) return; // 加载 .fly 工程文件 terraExplorer.Load(projectPath); } }Initialize方法接受四个参数第一个是连接模式TE_CONNECTION_NEW表示启动一个新的 TerraExplorer 实例第二个是窗口标题第三、第四个是预留的用户名密码本地开发不用填。返回的terraExplorer对象是主程序控制接口后续加载工程、关闭场景、设置视口都靠它。代码里的SGWorld70是 TerraExplorer 7.x 版本的接口类。如果安装的是 6.x类名会变成SGWorld60命名规则就是 API 版本号后缀。判断当前版本的方式很简单——安装目录下 TerraExplorerX.dll 的文件属性里能看到版本号或者直接看帮助文档里的接口定义。类名写错是新手最容易碰到的问题编译时提示找不到类型往往不是引用缺失而是版本号不对。3. 理解 API 的对象模型SGWorld 之下藏着哪几类“管家”3.1 五大接口对象工程、对象、导航、地形与分析TerraExplorer 的 COM 接口设计思路是典型的“门面模式”SGWorld 作为门面背后挂着五个核心对象分别掌管场景的不同方面。IProject70负责工程文件的生命周期包括打开、保存、关闭以及工程里各类图层的访问。IObjects70是操作三维对象的总入口创建标注、多边形、模型、路径动画都走它。INavigate70控制视口相机飞行、缩放、旋转、定位全部由它完成。ITerrain70提供地形相关能力比如获取指定经纬度的高程值。IAnalysis70做空间分析量测、通视分析、洪水淹没分析都在这一层。理解这个结构很重要因为 C# 代码里你拿到接口的方式几乎是固定的——先SGWorld再点出对应的属性。没有记忆负担但容易混淆的是每个接口能做什么、不能做什么。比如创建三维对象要用IObjects70而移动已有对象却要拿到该对象的IPosition70接口两者不是一回事。3.2 从 SGWorld 拿到对象的常见调用链C# 代码里最常见的调用链是“先拿工程、再拿对象、再拿具体类型”。TerraExplorer 的接口设计不像 C# 里 List 或 Dictionary 那么直观它是 COM 风格的一级级属性访问。下面这段代码展示了如何从 SGWorld 定位到一个具体的三维对象public void FindObject(string objectId) { // 从 SGWorld 拿工程接口 IProject70 project sgWorld.Project; // 从工程接口拿对象管理接口 IObjects70 objects project.Objects; // 按 ID 查找对象返回通用对象接口 IObject70 obj objects.GetObject(objectId); if (obj null) { Console.WriteLine(未找到对象: objectId); return; } // 将通用接口转为具体的三维对象接口 ITerrainObject70 terrainObj obj as ITerrainObject70; if (terrainObj ! null) { // 获取对象位置 IPosition70 pos terrainObj.Position; Console.WriteLine(经度: pos.X , 纬度: pos.Y); } }GetObject的参数是对象的唯一标识符这个 ID 在创建对象时由 TerraExplorer 自动生成也可以在工程文件里查到。返回的IObject70是所有对象的基接口实际使用时要向下转型。IPosition70暴露了坐标和朝向X是经度Y是纬度Z是高程单位和 WGS84 经纬度一致。这段代码展示了 TerraExplorer C# 开发的典型节奏接口即阶梯每一步都要先拿上级接口再往下访问具体能力。想做二次开发先花半小时把这张对象关系图印在脑子里比急着敲代码更值。4. 用 C# 示例代码落地三个高频功能加载数据、创建对象、控制飞行4.1 加载本地数据到三维场景TerraExplorer 支持加载多种数据源最常见的是影像、地形和三维模型。C# 代码里通过IProject70的AddLayer方法把数据挂到工程上。数据加载是异步的大文件可能需要几秒到几十秒代码里要处理加载状态查询。public bool AddLayer(string filePath, string layerName) { // 获取图层管理接口 IProject70 project sgWorld.Project; // AddLayer 的参数文件路径、图层名称、父图层ID0表示顶层 string layerId project.AddLayer(filePath, layerName, 0); if (string.IsNullOrEmpty(layerId)) { Console.WriteLine(图层添加失败: filePath); return false; } // 等待图层加载完成最多等 30 秒 for (int i 0; i 60; i) { System.Threading.Thread.Sleep(500); ILayer70 layer project.GetLayer(layerId); if (layer ! null layer.Loading 0) { Console.WriteLine(图层加载完成ID: layerId); return true; } } return false; }参数说明AddLayer的第一个参数是文件完整路径支持 .tif、.jp2、.osgb、.3ds 等 TerraExplorer 能识别的格式第二个参数是图层在图层树里显示的名称第三个参数是父图层 ID传 0 表示放在最顶层。GetLayer返回的ILayer70接口里有个Loading属性0 表示加载完成1 表示加载中2 表示失败。这里有个很容易忽视的点数据文件路径不能包含中文或特殊字符。TerraExplorer 底层处理中文路径时偶尔会出问题表现为图层添加成功但内容不显示。我习惯先把数据拷贝到英文路径下再加载省掉一整天排查时间。4.2 创建标注对象并在指定经纬度显示创建对象是 TerraExplorer 二次开发里最常用的能力。标注、点、线、面、模型都可以通过IObjects70创建。标注对象适合做设备点位、事件点位展示在数字孪生项目里频率极高。public string CreateLabel(double lon, double lat, string text) { // 获取对象管理接口 IObjects70 objects sgWorld.Project.Objects; // 创建标注对象参数位置、图标、文字内容、高度偏移 IObject70 obj objects.CreateLabel( lon, lat, 0, // 经度、纬度、高度 , // 图标为空表示使用默认图标 text, // 标注文字 100); // 文字显示的最大距离米 if (obj null) { Console.WriteLine(标注创建失败); return null; } // 设置标注颜色和大小 IPosition70 pos obj.Position; IWorldPosition70 worldPos pos.Position; worldPos.Altitude 5; // 将标注抬高 5 米避免被地形遮挡 pos.Position worldPos; return obj.ID; }CreateLabel的参数依次是经纬度、高度、图标路径、文字内容和显示距离。图标可以传本地图片路径也可以传空字符串用默认图标。返回值是IObject70接口通过obj.ID能拿到这个标注对象的字符串 ID后续想修改、删除、查询它都要用这个 ID。修改位置时要注意IPosition70和IWorldPosition70是两层结构。Position属性返回的是当前位置快照修改快照后要再赋值回去才生效。这是 TerraExplorer COM 接口里为数不多需要用“读-改-写”三次操作的场景C# 习惯了对象引用直接改属性在这里容易写出只读不改的代码。4.3 通过导航接口控制视口飞行飞行控制是 TerraExplorer 最有价值的能力之一。它能模拟飞机视角从高空俯冲到目标点位在应急指挥、巡检场景里非常加分。INavigate70接口的FlyTo方法就是干这个的。public void FlyTo(double lon, double lat, double altitude, double distance) { // 获取导航接口 INavigate70 navigate sgWorld.Navigate; // FlyTo 参数目标经度、纬度、高度、飞行距离、飞行速度、等待时间 navigate.FlyTo( lon, lat, altitude, // 目标经纬度和高度 distance, // 视点距离目标点水平距离米 TE_NAVIGATION_MODE.TE_FLY_TO, // 飞行模式 1.0); // 飞行速度系数1.0 为默认速度 // 等待飞行完成此时可以查询相机状态 System.Threading.Thread.Sleep(2000); Console.WriteLine(当前视口经度: navigate.Position.X); Console.WriteLine(当前视口纬度: navigate.Position.Y); }TE_NAVIGATION_MODE枚举里还有TE_JUMP_TO两者区别是TE_JUMP_TO瞬间切换视口没有飞行动画适合程序里快速定位TE_FLY_TO带平滑过渡适合演示场景。速度系数是浮点数1.0 是默认速度2.0 是两倍速0.5 是半速。navigate.Position返回的是当前视口相机的位置不是飞机本身的位置。想确认飞行是否完成最稳妥的办法是拿当前位置和目标位置做距离判断而不是固定 Sleep 几秒因为飞行时长跟距离和速度系数有关。无人机巡线项目里我一般写个异步方法轮询距离差小于 5 米时认为到达。5. TerraExplorer 二次开发避坑C# 调用 COM 接口时最容易翻车的 5 个地方5.1 现象SGWorld 初始化后拿不到接口刚接触 TerraExplorer 时最容易遇到的情况是Initialize返回 null或者调用sgWorld.Project时报System.Runtime.InteropServices.COMException。排错时检查两件事第一TerraExplorer 主程序是否已经启动。TE_CONNECTION_NEW模式会创建一个新实例但前提是运行库注册正常第二你的 C# 平台目标是否和运行库一致。x64 工程调用 32 位 OCX 控件几乎必崩。另一种情况是开发机上装了多个版本注册表里 COM 组件指向了旧版代码却引用了新版的 Interop 程序集。解决方法是打开 TerraExplorer 主程序确认它能正常运行再检查工程引用里的 Interop 版本。仍不行就打开 Visual Studio 的“开发者命令提示符”执行regsvr32 /u再regsvr32手动注册一次 TerraExplorerX.dll。这类问题九成是环境问题代码本身反而没毛病。5.2 现象C# 事件回调不触发TerraExplorer 的 COM 接口允许注册事件比如鼠标点击场景、相机位置变化、对象被选中。C# 里用委托绑定事件方式注册可经常发现回调函数一直不被执行。这通常不是代码问题而是调用了Initialize的线程和创建 SGWorld 的线程不是同一个或者创建 SGWorld 的线程在事件注册后被关闭了。TerraExplorer 的事件回调机制依赖消息泵纯控制台程序里没有 Windows 消息循环事件永远派发不过来。解决方法是把 SGWorld 初始化放在 WinForms 或 WPF 的 UI 线程上或者在控制台程序里加Application.Run()启动消息循环。我用过一个笨办法开一个隐藏的 Form 在后台跑所有 TerraExplorer 调用都通过Control.BeginInvoke封送过去事件就正常了。5.3 现象程序运行几小时后突然崩溃TerraExplorer 的 COM 接口是非托管资源C# 里没有自动释放机制。长时间运行的应用如果不停创建对象而不释放句柄泄漏会导致数小时后内存暴增然后突然崩溃。排查时打开任务管理器观察 TerraExplorer 进程内存会发现它像只吃不吐的怪兽。这是 COM 组件最常见的通病TerraExplorer 也继承了这一点。解决方案是显式调用 Marshal 释放。用System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)释放不再使用的 COM 对象并在 finally 块里做兜底。常规做法是封装一个ReleaseTerrainObject辅助方法每次用完对象就调用能有效把内存控制在稳定水平。5.4 现象创建的标注或模型飞到海里去创建对象时经纬度坐标填得很认真可对象就是显示在莫名其妙的地方甚至直接扎进海里。这类问题几乎都是高程问题。TerraExplorer 的地形默认带真实高程创建对象时如果不指定高程它会落到 0 米在海域就是水下在山地就是穿到山体内部。解决方法是先通过ITerrain70.GetHeight获取目标点位的地形高程再把高程值传给创建方法。public string CreateLabelOnTerrain(double lon, double lat) { ITerrain70 terrain sgWorld.Terrain; // 获取地形高程参数为经纬度和栅格精度 double height terrain.GetHeight(lon, lat, TE_ALTITUDE_TYPE.TE_ALTITUDE_ABSOLUTE); // 在真实地形高程上加 5 米避免被地形遮挡 IObjects70 objects sgWorld.Project.Objects; IObject70 obj objects.CreateLabel(lon, lat, height 5, , 点, 100); return obj.ID; }GetHeight第三个参数是采样模式TE_ALTITUDE_ABSOLUTE表示绝对高程TE_ALTITUDE_GROUND表示贴地高程。做标注、模型摆放时永远先用GetHeight拉起真实高程再叠加一个避让高度这是最靠谱的做法。5.5 现象加载图层后场景空白图层加载完成、代码没报错场景里却什么都看不见。先检查图层是否被设置为可见ILayer70.Visible属性被误设为 false 时就会这样。其次是渲染状态问题TerraExplorer 加载数据后 GPU 驱动有时不及时刷新调用terraExplorer.Render(true)强制重绘往往能解决。排除这两个问题后剩下的可能是影像投影问题——直接投影的影像在 TerraExplorer 里显示不出来需要先用工具转成 TerraExplorer 识别的投影方式。处理时可以先加载自带示例数据验证场景本身没问题再叠加业务数据这样能把数据转换和代码逻辑的问题隔离开。6. 进阶把 C# 上位机思路搬进三维场景6.1 用 C# 委托和事件把飞行状态推送到 UITerraExplorer 的导航控制是同步的FlyTo 方法执行后要自己轮询视口位置。实际项目里我更习惯把这种轮询封装成带事件通知的类飞行到位后自动触发 UI 更新。这是 C# 上位机开发的常规做法在 TerraExplorer 里同样适用。public class FlyToStatusNotifier { private INavigate70 _navigate; private Timer _timer; public event Actionbool OnArrived; public void StartWatch(double targetLon, double targetLat, double threshold) { _navigate sgWorld.Navigate; _timer new Timer(); _timer.Interval 500; _timer.Tick (s, e) { var pos _navigate.Position; double dx pos.X - targetLon; double dy pos.Y - targetLat; double dist Math.Sqrt(dx * dx dy * dy); if (dist threshold) { OnArrived?.Invoke(true); _timer.Stop(); } }; _timer.Start(); } }6.2 批量生成坐标点的小工具写法数字孪生项目通常要一次性在地图上打几百个点位。循环调用CreateLabel是可行的但性能上要优化创建对象前先关闭场景重绘批量完成后再刷新。这个技巧能显著缩短批量生成时间。public void BatchCreateLabels(string[] csvLines) { // 暂停场景刷新批量操作时提升性能 terraExplorer.SetRenderEnabled(false); try { foreach (var line in csvLines) { var parts line.Split(,); string lon parts[0]; string lat parts[1]; string name parts[2]; CreateLabelOnTerrain(Convert.ToDouble(lon), Convert.ToDouble(lat)); } } finally { // 重新开启刷新并强制渲染 terraExplorer.SetRenderEnabled(true); terraExplorer.Render(true); } }CSV 文件里每行是“经度,纬度,名称”的结构批量创建时把批量的三维对象放进同名的图层里方便管理。做这套接手的实战项目时我最大的教训就是TerraExplorer 的 C# 开发难点不在 C# 而在 COM 接口的思维转换——它不是托管代码释放、事件、线程都得按 COM 的老规矩来。第一次接手时我花了大半天纠结对象位置改不生效后来发现是忘了把修改完的位置对象赋值回去。这类问题没有捷径只能在实际调用的接口上多试几次把错误信息逐条记下来。建议你拿到环境后先跑通初始化加载工程创建标注这三步再往里填业务逻辑。这个方向值不值得投入做完这三个步骤你就有答案了。希望帮到你。本文还有配套的精品资源点击获取
返回列表