简介:这份资源是面向GIS与三维可视化开发者的TerraExplorer二次开发C#示例代码包,基于Skyline平台,适合已掌握C#基础语法与面向对象编程、希望快速上手TerraExplorer SDK的中级开发者。包内共21个文件,以cs源码、resx资源文件、csproj工程文件与sln解决方案为主,另含shp、dbf、shx、prj等Shapefile数据及fly飞行文件,压缩包约42KB,可直接在Visual Studio中打开运行。示例覆盖地图对象与控件操作、事件驱动编程、多格式数据集成、3D模型加载、空间查询与缓冲区分析、自定义界面及多线程异步处理等核心知识点,并配有工程配置与资源文件,便于对照调试与二次修改。目前已有483人学习下载,可作为理解TerraExplorer API调用方式、搭建三维GIS应用原型的实用参考。
1. 从一份 C# 示例代码说起:TerraExplorer 二次开发到底能做什么
如果你手上正好有一份 TerraExplorer 的 C# 示例代码包,却不知道从哪下手,或者你正准备基于 Skyline 做三维 GIS 的二次开发,那这份资源值得花时间拆一遍。TerraExplorer 本身是一套成熟的三维地球可视化平台,而它的二次开发能力,核心就在于通过 API 去操控三维场景里的相机、图层、标注、飞行路径这些对象。这份 C# 示例代码包,本质上就是把这些 API 的调用方式用一个个可运行的小例子串了起来,覆盖了从连接 TerraExplorer 进程、加载地形影像、创建动态对象,到响应鼠标事件、控制飞行漫游的完整链路。适合谁?一是做三维 GIS 桌面端或 C/S 架构的开发者,二是需要把三维场景嵌入到已有业务系统里的团队,三是想快速验证 Skyline 能力边界的技术选型人员。下面我按“能干什么、怎么跑、怎么改”的顺序,把这份代码包拆开讲。
2. 环境搭建与项目结构:从零把示例跑起来
2.1 运行前必须确认的三件事
TerraExplorer 的二次开发和普通 .NET 项目最大的区别在于:你的 C# 程序并不是独立运行的,它需要和一个已经安装好的 TerraExplorer 客户端(或者 TerraExplorer Pro)建立通信。所以第一件事,确认本机装好了对应版本的 TerraExplorer,并且能正常打开三维地球。第二件事,确认你的项目目标平台和 TerraExplorer 的位数一致——常见做法是统一用 x86,因为不少版本的 TerraExplorer COM 组件只注册了 32 位。第三件事,在 Visual Studio 里把目标框架设成 .NET Framework 4.x,而不是 .NET Core 或 .NET 5+,因为这套 API 底层是 COM 组件,跨平台运行时支持很有限。
提示:如果你在 64 位系统上跑,项目属性里把“首选 32 位”勾上,能省掉一大半“类未注册”的玄学问题。
2.2 引用与初始化:连接 TerraExplorer 的第一段代码
示例代码里最常见的入口,是通过TerraExplorerX这个 COM 库拿到IApplication接口,然后进一步拿到IProject和INavigate等对象。下面这段是典型的初始化逻辑,我按示例里的写法整理了一下:
// 引入 TerraExplorerX 命名空间(需先在引用中添加 COM 组件) using TerraExplorerX; class Program { static void Main(string[] args) { // 创建应用对象,此时会尝试连接已运行的 TerraExplorer 实例 IApplication app = new ApplicationClass(); // 拿到当前打开的项目(工程),后续所有场景操作都从这里出发 IProject project = app.Project; // 拿到导航控制接口,用于控制相机位置、视角、飞行等 INavigate navigate = project.Navigate; // 示例:把相机飞到指定经纬度和高度 navigate.FlyTo( "北京", // 目标名称,可以是地名或坐标描述 116.397, // 经度 39.909, // 纬度 5000, // 高度(米) 0, // 朝向 -45, // 俯仰角 5.0 // 飞行持续时间(秒) ); // 释放 COM 对象,避免进程残留 System.Runtime.InteropServices.Marshal.ReleaseComObject(app); } }这段代码的逻辑很直白:先连上 TerraExplorer,再拿到项目对象,最后调用FlyTo做一次相机飞行。参数里最容易翻车的是高度和俯仰角——高度单位是米,俯仰角是度数,负值表示向下看。如果你发现相机没动,先检查 TerraExplorer 是不是已经打开了某个工程,空工程状态下很多接口会直接抛异常。
2.3 目录结构:示例包一般怎么组织
这类 C# 示例代码包,目录结构通常不会太复杂,但有几个关键文件夹值得先扫一眼。常见做法是:
References/或Lib/:放 TerraExplorerX 的互操作程序集(Interop),有些包会直接给 DLL,有些只给引用说明。Samples/或Demos/:按功能分文件夹,比如CameraControl、DynamicObject、LayerManagement、MouseEvent。Resources/:放示例用到的地形、影像、模型文件,或者 fly 文件(飞行路径文件)。Docs/:偶尔会有简单的 API 说明或运行截图。
拿到包之后,我一般先打开Samples里最基础的那个,确认能编译通过、能连上 TerraExplorer,再去翻其他模块。不要一上来就啃动态对象或自定义图层,那些依赖前置初始化。
2.4 编译与运行:第一次跑通的检查清单
编译时如果报“类型未定义”或“命名空间不存在”,九成是 COM 引用没加对。正确做法是在“引用”里右键“添加 COM 引用”,找到TerraExplorerX相关的那一项,勾选后 VS 会自动生成 Interop 程序集。运行阶段,先手动打开 TerraExplorer 并加载一个工程,再启动你的 C# 程序。如果程序一闪而过,在Main末尾加Console.ReadLine()挂住,看有没有异常输出。第一次跑通的标准很简单:相机能飞到指定位置,控制台不报错,TerraExplorer 进程不崩。
3. 核心模块拆解:相机、图层与动态对象
3.1 相机控制:FlyTo 与 FlyAround 的参数差异
相机控制是三维场景里最常用的功能,示例代码里一般会给出FlyTo、FlyAround、SetPosition几个方法。FlyTo是点到点飞行,适合快速定位;FlyAround是绕某点旋转,适合做环视展示。两者的参数结构不一样,FlyAround需要指定中心点、半径、旋转速度和圈数。下面是一个绕点飞行的示例:
// 绕指定点做环绕飞行 navigate.FlyAround( 116.397, 39.909, 0, // 中心点经纬度和高度 2000, // 环绕半径(米) 30, // 环绕速度(度/秒) 2, // 环绕圈数 10 // 总持续时间(秒) );参数里半径和速度要匹配,半径太小速度太快,画面会晕。常见做法是半径 1000~3000 米,速度 20~40 度/秒。如果你要做自动巡航,把多个FlyTo串起来,中间加Thread.Sleep或事件回调,别用死循环硬等。
3.2 图层管理:加载地形、影像与矢量
TerraExplorer 的图层体系分好几类:地形图层、影像图层、矢量图层、模型图层。示例代码里通常通过project.CreateLayer或project.Open来加载。以加载一个本地影像文件为例:
// 创建影像图层并加载本地文件 ILayer layer = project.CreateLayer( LayerTypeCode.ImageLayer, // 图层类型:影像 "MyImageLayer", // 图层名称 0 // 图层组索引 ); // 设置影像文件路径(支持常见格式如 tif、jpg 等) layer.FileName = @"C:\Data\ortho.tif"; // 把图层添加到当前工程 project.AddLayer(layer);这里的关键是LayerTypeCode要选对,影像和地形的加载方式不同。另外,文件路径最好用绝对路径,相对路径在 TerraExplorer 进程里解析时容易找不到。加载完成后,可以用layer.Visible控制显隐,用layer.Opacity调透明度。
3.3 动态对象:创建标注、模型与轨迹
动态对象是二次开发里最灵活的部分。示例代码一般会演示如何创建一个IMesh或ILabel,然后设置位置、样式和动画。下面是一个创建动态标注的片段:
// 在指定位置创建一个文字标注 ILabel label = project.CreateLabel( "TargetLabel", // 标注名称 116.397, 39.909, 100, // 经纬度和高度 "目标点", // 显示文字 0 // 样式索引 ); // 设置标注的字体大小和颜色 label.FontSize = 14; label.TextColor = 0x00FF00; // 绿色 // 让标注始终面向相机 label.AlwaysFaceCamera = true;动态对象创建后,可以通过label.Position实时更新位置,做轨迹回放时特别有用。如果你要做大量动态点,注意及时释放不用的对象,否则 TerraExplorer 的内存会涨得很快。
3.4 鼠标与键盘事件:让场景响应用户操作
示例代码里通常会有一个MouseEvent或Interaction的文件夹,演示如何捕获鼠标点击、移动和键盘按键。核心是给project或navigate挂事件处理器:
// 订阅鼠标点击事件 project.OnMouseClick += new _IProjectEvents_OnMouseClickEventHandler(Project_OnMouseClick); void Project_OnMouseClick(int x, int y, int flags) { // 把屏幕坐标转成三维世界坐标 double lon, lat, alt; project.ScreenToWorld(x, y, out lon, out lat, out alt); // 在点击位置创建一个临时标注 project.CreateLabel("ClickPoint", lon, lat, alt, "点击位置", 0); }这里ScreenToWorld是高频接口,参数是屏幕像素坐标,输出经纬度和高度。注意事件回调里不要做耗时操作,否则会卡住 TerraExplorer 的主线程。常见做法是把数据丢到队列里,另起线程处理。
4. 避坑与常见问题:那些让我熬夜的报错
4.1 “类未注册”或“COM 组件加载失败”
现象:程序一运行就抛COMException,提示类未注册。原因:TerraExplorer 的 COM 组件没有正确注册,或者你的项目位数和组件不匹配。解决:先确认 TerraExplorer 安装完整,然后用管理员权限运行一次 TerraExplorer,让它完成组件注册。如果还不行,在 VS 里把目标平台改成 x86,重新生成。
4.2 相机飞行没反应,但代码不报错
现象:调用FlyTo后画面不动,控制台也没有异常。原因:TerraExplorer 当前没有打开任何工程,或者Navigate对象拿到的不是活动窗口。解决:在调用飞行前,先判断project是否为空,并确保 TerraExplorer 已经加载了一个 fly 或工程文件。常见做法是在程序启动时加一个Thread.Sleep(2000)等 TerraExplorer 初始化完。
4.3 动态对象创建后不显示
现象:CreateLabel或CreateMesh返回了对象,但场景里看不到。原因:对象的高度或坐标超出了当前视野,或者图层被隐藏了。解决:先调用navigate.FlyTo飞到对象位置,再检查label.Visible属性。另外,有些版本的 TerraExplorer 要求动态对象必须添加到某个图层组里才会渲染。
4.4 内存持续增长,程序越来越卡
现象:长时间运行后,TerraExplorer 进程内存飙升,操作变慢。原因:COM 对象没有及时释放,尤其是循环里创建的动态对象。解决:每次用完ILabel、IMesh这类对象后,调用Marshal.ReleaseComObject释放。如果是在循环里创建,考虑复用对象而不是反复新建。
4.5 事件回调里操作 UI 导致死锁
现象:在鼠标事件里直接更新 WinForm 控件,程序卡死。原因:TerraExplorer 的事件回调运行在 COM 线程上,直接跨线程操作 UI 会死锁。解决:用Control.Invoke或Dispatcher.Invoke把 UI 更新切回主线程。我一般会封装一个SafeInvoke方法,所有事件里的 UI 操作都走它。
5. 二次开发改造:从示例到业务系统的最后一公里
把示例代码跑通只是第一步,真正要落到业务系统里,得做几件事。第一,封装一个 TerraExplorer 的连接管理类,统一处理初始化、异常重连和释放,别在每个窗体里都写一遍new ApplicationClass()。第二,把相机控制、图层加载、动态对象创建这些操作抽象成服务接口,业务层只调接口,不直接碰 COM 对象。第三,事件处理要加节流和队列,避免高频鼠标事件把主线程打满。第四,配置文件里把常用参数(默认飞行高度、图层路径、超时时间)抽出来,换环境时不用改代码。
下面是一个简单的连接管理类骨架,我一般会这么写:
public class TerraConnection : IDisposable { private IApplication _app; private IProject _project; public IProject Project => _project; public bool Connect() { try { _app = new ApplicationClass(); _project = _app.Project; return _project != null; } catch (COMException ex) { // 记录日志,返回失败 Console.WriteLine("连接失败:" + ex.Message); return false; } } public void Dispose() { if (_project != null) { Marshal.ReleaseComObject(_project); _project = null; } if (_app != null) { Marshal.ReleaseComObject(_app); _app = null; } } }改造完之后,验证方法也很直接:写一个控制台程序,连续调用 100 次飞行和对象创建,观察内存是否稳定、TerraExplorer 是否崩溃。如果稳定,再往 WinForm 或 WPF 里集成。从那以后我每次拿到新的 TerraExplorer 示例包,都强制先跑通连接管理类,再去看具体功能模块,这样能少走很多弯路。希望帮到你。
本文还有配套的精品资源,点击获取