简介面向具备一定C#基础、希望基于Skyline平台开展三维GIS定制开发的开发者这份TerraExplorer二次开发示例代码围绕SDK调用、事件驱动编程、地图对象与控件操作、多格式数据集成、3D模型加载、空间查询分析及自定义界面等核心知识点组织可帮助读者理解如何将C#与TerraExplorer API结合完成从地图加载到场景交互的完整开发流程。压缩包共21个文件约42KB以cs源码文件为主辅以resx资源、csproj与sln工程文件并附带shp、shx、dbf、prj等Shapefile数据及fly飞行文件便于直接打开工程对照调试。目前已有484人学习下载适合作为入门参考与功能验证的起点读者可从中获取可运行的工程骨架、典型API调用范例与数据加载思路减少自行摸索SDK的成本。1. 从一份 C# 示例包说起TerraExplorer 二次开发到底能做什么如果你手头拿到的是一个三维地理信息平台的 C# 示例工程第一反应大概率是“这堆文件从哪看起”。TerraExplorer 这类平台的核心价值不在它自带的浏览界面而在于它对外暴露的 COM 接口——你能用 C# 把三维场景、图层、标注、飞行路径、动态对象全部用代码驱动起来。这份示例代码包就是干这个的它把常见的二次开发动作拆成一个个可运行的小工程覆盖场景加载、对象创建、事件响应、界面嵌入几条主线。适合谁适合已经能打开平台、但不知道怎么把业务逻辑塞进三维场景里的开发者也适合需要把三维视图嵌进自己 WinForms 或 WPF 桌面程序的团队。下面按“先看清结构、再跑起来、最后动手改”的顺序拆。2. 工程结构与技术栈先搞清楚这堆文件谁调谁2.1 示例包的典型目录布局拿到一个 C# 二次开发示例包别急着双击 sln。先看目录它基本决定了你后面改代码时该往哪放。这类示例通常长这样TerraExplorerSamples/ ├── Samples.sln ├── Common/ │ ├── ComInterop/ # 平台 COM 接口的互操作封装 │ └── Helpers/ # 坐标转换、路径拼接等工具类 ├── BasicScene/ # 场景加载与相机控制示例 ├── ObjectCreate/ # 三维对象、标注、模型创建示例 ├── EventDemo/ # 事件订阅与回调示例 ├── EmbedView/ # 把三维视图嵌入自有窗体示例 └── packages.config / *.csprojCommon是公共层互操作封装和工具类都在这改任何示例前先确认它引用的平台组件版本和你本机装的一致。BasicScene到EmbedView是按功能切的独立工程每个都能单独启动互不依赖。这种“一个功能一个工程”的组织方式对二次开发很友好——你要验证某个接口行为直接跑对应工程不用在巨型项目里翻。2.2 技术栈与运行前提这套示例的技术栈很集中C# .NET Framework多数是 4.x 平台 COM 组件 WinForms 宿主。没有 Web 那一套也没有跨平台诉求。运行前提有三条必须满足本机装了对应版本的三维平台运行时示例工程的目标平台x86/x64和平台组件位数一致COM 组件已正确注册。第三条是最容易翻车的地方后面避坑章会细说。提示位数不一致是这类 COM 互操作项目最常见的启动失败原因先确认再调代码。2.3 互操作层是怎么封装的示例里对平台接口的调用不会直接裸写dynamic而是走一层封装。典型写法是用[ComImport]声明接口再用一个工厂类统一创建和释放// ComInterop/SceneFactory.cs [ComImport] [Guid(...)] // 平台接口的 GUID示例里已填好 [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface ISceneControl { void LoadScene(string filePath); // 加载场景文件 void SetCamera(double lon, double lat, double alt); // 相机定位 object CreateObject(string typeName); // 按类型创建对象 } public static class SceneFactory { public static ISceneControl Create() { // 通过平台提供的 ProgID 创建实例 var type Type.GetTypeFromProgID(Platform.SceneControl); if (type null) throw new InvalidOperationException(平台组件未注册); return (ISceneControl)Activator.CreateInstance(type); } }这段代码的关键在Type.GetTypeFromProgID它按注册表里的 ProgID 找组件找不到就说明运行时没装或没注册。Guid特性里的 GUID 必须和平台文档一致示例里已经填好你改的时候别动。CreateObject返回object实际用的时候要按具体对象类型再转一次这是 COM 互操作的常态不是示例写得糙。2.4 一个示例工程的启动链路以BasicScene为例启动链路是Program.Main→ 创建主窗体 → 窗体Load事件里调SceneFactory.Create()→ 调LoadScene加载示例场景 → 订阅相机变化事件 → 界面就绪。这条链路里每一步都能断点排查时按顺序看哪个环节返回空或抛异常比盲猜快得多。示例把“创建—加载—订阅”三步拆得很清楚你二次开发时新增功能照着这个顺序往链路里插就行。3. 把示例跑起来从环境检查到第一个场景3.1 环境检查清单跑之前先过一遍清单能省掉大半折腾平台运行时是否安装、组件是否注册、示例目标平台位数、.NET Framework 版本、示例场景文件路径是否存在。前两项用平台自带的检测工具或注册表确认后三项在工程属性里看。示例场景文件通常是相对路径引用挪动工程目录后路径会失效这是新手最常踩的坑之一。3.2 编译与首次运行用 Visual Studio 打开Samples.sln先只编译BasicScene一个工程别一上来全量编译。全量编译会把所有示例的依赖都拉一遍任何一个工程配置不对都会报错干扰判断。单工程编译通过后直接 F5正常情况会弹出窗体并加载出三维场景。如果窗体出来了但场景是黑的先看场景文件路径再看相机初始位置是否落在有效范围内。# 命令行编译单个工程在解决方案目录下 msbuild BasicScene\BasicScene.csproj /p:ConfigurationDebug /p:Platformx86/p:Platformx86要和平台组件位数一致示例默认可能是AnyCPU跑 COM 组件时AnyCPU在 64 位系统上会按 64 位加载位数不匹配就起不来。这条命令的含义是显式指定目标平台避免运行时位数错配。3.3 相机控制示例怎么读BasicScene里最值得先读的是相机控制那段因为它把“坐标怎么传、视角怎么设”讲得最直白// 设置相机到指定经纬度和高度并朝向某个方位角 scene.SetCamera(116.39, 39.90, 500); // 经度、纬度、高度米 scene.SetCameraOrientation(45.0, -30.0); // 方位角、俯仰角度SetCamera三个参数单位分别是度、度、米别把高度当成公里传传大了相机直接飞到外太空场景看着就是空的。SetCameraOrientation的俯仰角负值表示向下看这个符号约定示例里没写注释但试一次就能确认。读示例代码时遇到这种“没注释但能试出来”的参数建议自己补一行注释再往下走后面改代码时省得再试一遍。3.4 对象创建示例的最小闭环ObjectCreate演示的是往场景里加东西最小闭环是“创建对象 → 设位置 → 设样式 → 加入场景”var marker scene.CreateObject(Marker); // 创建标注对象 marker.SetPosition(116.39, 39.90, 100); // 经纬度 高度 marker.SetProperty(Color, #FF0000); // 样式属性按字符串键设置 scene.AddObject(marker); // 真正加入场景才可见四步里最容易漏的是最后一步AddObject对象创建了但没加入场景界面上什么都不会出现排查时会误以为创建失败。SetProperty的键名是平台约定的字符串示例里用到的键可以直接抄没用到的查平台接口文档。这套“创建—配置—加入”的模式在后续所有对象类型上都通用读通一个就等于读通一类。4. 核心模块拆解事件、嵌入与数据交互4.1 事件订阅与回调线程三维场景是动态的相机移动、对象点击、场景加载完成都会触发事件。示例里的事件订阅写法是标准的 .NET 事件模式// 订阅相机变化事件 scene.CameraChanged OnCameraChanged; private void OnCameraChanged(double lon, double lat, double alt) { // 回调可能不在 UI 线程更新控件前要切回来 if (InvokeRequired) { BeginInvoke(new Action(() UpdateStatus(lon, lat, alt))); return; } UpdateStatus(lon, lat, alt); }关键点在InvokeRequired那段COM 组件的回调线程不保证是 UI 线程直接更新控件会抛跨线程异常。示例里加了线程切换你二次开发时新增事件处理也要照这个模式写。漏了这一步程序跑起来看着正常一操作就崩属于典型的“玄学崩溃”其实是线程问题。4.2 把三维视图嵌进自有窗体EmbedView是这份示例里对二次开发最有价值的部分它演示怎么把平台的三维视图作为控件嵌进你自己的 WinForms 界面而不是单独弹一个平台窗口。核心步骤是创建宿主控件、设置父窗口句柄、通知平台完成嵌入var host new Panel { Dock DockStyle.Fill }; this.Controls.Add(host); // 把平台视图的父窗口设为宿主控件句柄 scene.SetParentWindow(host.Handle); scene.Resize(host.Width, host.Height); // 首次嵌入后手动同步尺寸SetParentWindow传的是 Win32 窗口句柄所以宿主必须是已有句柄的控件Panel在加入窗体后才有句柄顺序不能反。Resize首次要手动调一次之后靠宿主控件的Resize事件转发给平台视图否则拖动窗体时三维视图尺寸不跟着变界面会很难看。这个“句柄 尺寸同步”的组合是嵌入类开发的标准套路记住这两步就够用了。4.3 场景数据与业务数据的桥接示例里对象和业务数据的关联方式值得单独说平台对象只负责“显示”业务属性存在你自己的字典或数据表里用对象 ID 做键关联。示例里能看到类似Dictionarystring, BizData的结构对象创建后把返回的 ID 存进去点击事件里拿 ID 反查业务数据。这种“显示与数据分离”的做法比把业务字段全塞进对象属性里更可控后面换数据源或加字段都不用动三维那层。4.4 资源释放与生命周期COM 对象用完要释放示例里在窗体关闭时统一调ReleaseComObject并置空引用。漏了这步反复打开关闭窗口会累积未释放的组件实例表现为内存上涨、偶尔卡死。示例的释放逻辑集中在窗体FormClosing里你新增的每个 COM 对象都要纳入同一套释放流程别散落在各处。5. 避坑与排查这几处翻车我见过太多次5.1 组件未注册导致创建实例失败现象SceneFactory.Create()抛InvalidOperationException提示组件未注册。原因平台运行时没装或装了但 COM 组件没注册换机器、重装系统后常见。解决确认运行时安装完整用平台自带的注册工具重新注册组件再跑一次。别急着重装整个平台先确认注册表里 ProgID 是否存在。5.2 位数不匹配导致进程直接退出现象编译通过一运行进程就退出没有明显异常信息。原因示例目标平台是AnyCPU在 64 位系统上按 64 位加载而平台组件是 32 位。解决把工程目标平台显式改成x86或与组件一致的位数重新编译。这个坑隐蔽在于它不报错只是静默退出第一次遇到很容易怀疑代码。5.3 跨线程更新控件引发崩溃现象操作场景时程序偶发崩溃堆栈指向控件更新。原因事件回调在非 UI 线程执行直接改了控件。解决所有事件处理里更新界面前加InvokeRequired判断并切回 UI 线程参照 4.1 的写法。偶发崩溃最难查养成“回调里先判线程”的习惯能省很多时间。5.4 场景文件路径失效导致黑屏现象窗体正常三维区域全黑。原因示例场景文件用相对路径工程目录挪动后路径失效。解决改成绝对路径或把场景文件随工程一起拷贝并在加载前判断文件是否存在不存在就给出明确提示别让它静默黑屏。5.5 嵌入后尺寸不同步现象三维视图嵌进自有窗体后拖动窗口时视图尺寸不变出现留白或裁切。原因只调了一次Resize没把宿主控件的Resize事件转发给平台视图。解决在宿主控件的Resize事件里调scene.Resize并注意首次嵌入后手动补一次。6. 二次开发改造从跑通示例到落地自己的功能跑通示例只是起点真正的工作是把示例里的模式套到自己的业务上。我的做法是先定一条最小改造路径选一个最接近需求的示例工程比如要做标注管理就选ObjectCreate在它基础上加一个自己的业务模块而不是从空工程重新搭。这样互操作封装、事件线程处理、资源释放这些容易出错的底层逻辑直接复用你只专注业务。改造时几个具体技巧。第一把Common层当稳定层业务代码不要往里塞互操作封装一旦被业务逻辑污染后面升级平台组件会很痛苦。第二新增对象类型时先写一个最小验证创建、设位置、加入场景、点击能拿到 ID四步都通了再往上叠业务属性。第三事件处理统一走一个分发器别在每个窗体里各写一套InvokeRequired集中处理线程切换和异常捕获出问题时只有一个地方要看。验证方法上我一般给每个改造点配一个可独立运行的验证入口比如一个只加载场景并创建单个对象的按钮点一下看结果。三维开发的问题往往表现为“界面没反应”没有独立验证入口就只能靠猜。下面这个表格是我常用的改造检查项检查项验证方式失败表现组件创建调SceneFactory.Create()抛异常或返回空场景加载加载后看相机是否就位黑屏对象可见创建后调AddObject无显示事件回调操作后看状态栏更新不更新或崩溃资源释放反复开关窗口看内存内存持续上涨有个教训值得说早期我图省事把业务数据直接塞进平台对象的自定义属性里结果换数据源时发现属性键名和平台版本绑定升级后一批键失效返工量很大。从那以后我每次做对象与数据关联都强制走“对象 ID 外部字典”这一层平台对象只当显示用。希望这些能帮你在拿到示例包时少走几步弯路。本文还有配套的精品资源点击获取