ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

OSGEarth开发环境搭建:3rdParty依赖库配置与FeatureNode点包含判断实战

OSGEarth开发环境搭建:3rdParty依赖库配置与FeatureNode点包含判断实战 简介这是为OSG3.6.5与OSGEarth 2.10准备的预编译三方库包基于Visual Studio 2019编译面向Windows平台上进行64位三维渲染与地球可视化开发的工程师可省去手动下载和编译依赖的繁琐过程。压缩包共含2000个文件其中1351个头文件h/hpp/inl提供接口声明56个lib和4个dll用于链接与运行32个cmake文件辅助工程配置另有少量c/proto/tcl等源文件与脚本压缩后整体大小约137.76MB。包内还包含大量时区数据文件方便涉及地理坐标与时间转换的场景直接调用。目前已有619人学习/下载适合需要快速搭建OSG/OSGEarth开发环境、并希望获得稳定预编译依赖的用户。这套预编译包已按VS2019 x64配置好解压后可直接引用头文件与库文件路径帮助开发者把精力集中在业务逻辑与场景搭建上。 做OSG3.6.5和OSGEarth开发的人几乎都绕不开这个开头源码明明下载好了CMake配置走到最后弹出一堆红色报错——找不到GDAL、找不到curl、找不到GEOS。当时我盯着那个3rdParty.zip的下载链接愣了半天才意识到这玩意儿不是可选项而是必选项。说白了OSGEarth全靠这些三方库撑起数据读取和空间计算预编译好的3rdParty.zip就是把这些最磨人的依赖一次性打包搞定让开发者能从“编译地狱”里解脱出来直接进入业务功能开发。这篇内容适合刚准备搭OSGOSGEarth开发环境、或者已经被依赖问题折腾到怀疑人生的朋友我会从为什么需要这个库、怎么选版本、怎么集成一直延伸到osgearth里一个很常被问到的问题如何判断一个点是否在FeatureNode内。1. 3rdParty三方库到底解决什么问题为什么绕不开1.1 OSGEarth的依赖全景大多数编译失败都倒在“找依赖”这一步OSGEarth不是一个独立引擎它建立在OSG之上同时又依赖一堆底层库来完成数据读写和空间运算。你编译OSGEarth时CMake会去系统里找GDAL、GEOS、curl、SQLite3、zlib、libpng、libjpeg、libtiff这些库。任何一个找不到配置阶段就直接报错连工程都生成不了。这里有个容易忽略的点OSGEarth对依赖库的版本是有要求的不是随便拿一个版本的GDAL都能编过编译器版本、运行库方式MD还是MT、Debug还是Release都必须和OSGEarth的编译选项一致。否则就算你装了对应的库也会在链接阶段跑出一堆“无法解析的外部符号”。当时我没经验试过自己去源码编译GDAL、GEOS、curl一套下来少说要半天中间还牵扯到zlib依赖、openssl依赖链很长稍微一个点没对上就得重来。1.2 预编译包和源码自编译的取舍预编译3rdParty.zip的优势非常明显省时间省心所有依赖库的版本组合已经被人验证过你只要保证OSG和OSGEarth版本匹配、VS版本匹配解压之后给CMake指个路径就能用。它的缺点也有灵活性差依赖库不能随便裁剪路径固定一般要求保持目录结构如果你要改GDAL源码或换版本预编译包就不适用了。我给新人的建议很直接如果你不是故意要研究底层库内部实现别自找麻烦直接用官方社区维护的预编译zip把时间留给业务代码。等真正跑通了、理解机制了再考虑自定义依赖。2. 选型前必看版本矩阵、位数与依赖清单2.1 VS版本与3rdParty的匹配关系OSG3.6.5和OSGEarth这个组合官方社区一般会同步放出对应VS2015、VS2017、VS2019的三方库预编译包。文件名里通常会带“VS2017”或“vc15”这类标识不同VS版本对应不同的C运行时。我踩过的典型坑是VS版本不匹配编译时能通过一部分但链接时出现“_ITERATOR_DEBUG_LEVEL不匹配”或者“运行库不匹配”的报错。原因很简单三方库是用某个VS版本特定运行库方式编译的你的OSG工程用的是另一套规则两边合不上。我实测比较稳的组合是OSG 3.6.5 OSGEarth 2.10分支 VS2017 x64 对应的3rdParty.zip。这套组合在社区里使用人数最多遇到问题也最容易搜到答案。2.2 依赖清单逐个说明GDAL/GEOS/curl分别管什么很多人只知道OSGEarth要几个库但不知道每个库万一起什么作用。我整理了一下依赖库作用缺了会怎样GDAL栅格与矢量数据格式读写地形、影像、SHP的底层IO打不开tif、shp等主流数据GEOS空间关系计算比如Contains、Intersects、BufferFeatureNode的空间查询做不了curl网络传输用于WMS、WMTS、TMS等在线数据源在线图层全部连不上SQLite3本地缓存和瓦片数据库存储缓存系统不可用zlib/libpng/libjpeg/libtiff图像解码与压缩常见图片格式打不开地形切片默认压缩失效拿GDAL举例OSGEarth读取高程图、影像图、矢量文件最终都走GDAL的驱动。哪怕你只是做一个离线的地形漫游没有GDAL也寸步难行。这也是为什么GDAL版本一旦和OSGEarth不一致会出现“读不出数据”这种让人摸不着头脑的问题。2.3 32位还是64位直接给结论能用64位就不要用32位。OSGEarth处理的数据量动辄上百MB甚至GB级别32位进程内存上限约2GB跑地形影像矢量叠加很容易爆内存。64位配合预编译zip里的x64库内存和性能都更稳。这里有个坑如果OSG工程编译成32位但3rdParty解压的是x64的库CMake配置阶段可能不报错生成的工程编译到链接期才炸错误信息还很难看懂。所以先确认好你的目标平台再选对应zip。3. 下载、解压与CMake集成一小时跑通3.1 目录结构与放置规范解压3rdParty.zip之后你会看到三个核心目录bin、include、lib。这三个目录必须保持在同一级因为CMake查找依赖时会通过根目录自动定位到这些子目录。我习惯把解压后的文件夹放在一个干净的路径下比如D:/OSG/3rdParty。注意两点路径不要带中文不要带空格否则CMake解析时可能出问题。另外如果你解压出来之后自己乱动目录结构比如把bin里的DLL单独拷到系统目录里很可能导致后续排查问题时更混乱。提示保留压缩包和原始解压路径记录。后面换机器或换环境时可以直接参考不用重新猜。3.2 CMake配置两个关键变量打开CMake GUI设置OSG源码目录和OSGEarth源码目录之后重点就来了。OSGEarth的CMake脚本会通过几个变量查找三方库常见的是ACTUAL_3RDPARTY_DIR或3RD_PARTY_DIR。具体做法点击“Add Entry”新建一个变量类型选PATH名称按版本选3RD_PARTY_DIR或ACTUAL_3RDPARTY_DIR。值填D:/OSG/3rdParty也就是解压出来的根目录。重新点Configure等待CMake查找依赖。如果变量找对了配置日志里会出现GDAL、GEOS、curl等库的路径状态是“found”。如果找不到先检查变量名是否和当前版本一致再看路径下是否有对应的include和lib目录。为了后续测试方便建议同时勾选BUILD_OSGEARTH_EXAMPLES这样编译完可以直接跑官方案例验证环境是否正常。3.3 生成解决方案与编译输出配置完成、没有红色报错后点Generate生成Visual Studio工程。打开生成的sln在解决方案管理器里找到ALL_BUILD切换到x64 Release直接生成。第一次编译时间会比较久主要是OSGEarth的插件模块多。如果只想快速验证三方库是否可用也可以只编译osgEarth这个核心工程和osgearth_viewer示例工程能省不少时间。编译完后建议再执行一下INSTALL工程把OSG和OSGEarth的产物统一输出到安装目录。后面自己写工程引用时只需要包含安装目录里的头文件和库文件不用去翻代码目录。4. 编译与运行期高频坑附快速排查表4.1 MD/MT运行库冲突最经典的链接错误预编译的3rdParty库默认是用动态运行库/MD编译的你的OSG工程也必须在CMake里保持默认的/MD选项不要手动改成/MT。如果你发现链接时报错里有_BEGIN_THREAD_ATTACH、_END_THREAD_ATTACH、__IMP____iob_func这类符号八成就是运行库方式不一致。这个坑我没少见尤其是有同学习惯在Visual Studio里把“运行库”改成“多线程(/MT)”来减小体积结果OSG和OSGEarth瞬间全部编译失败。Debug和Release也要严格区分DEBUG版本的工程必须链接*d.lib的库Release版本则链接普通.lib。3rdParty里一般都同时提供了两种库别搞混。4.2 运行时找不到DLL三个路径要理清编译过了不代表万事大吉运行时经常遇到“找不到OSG DLL”或者“找不到GDAL DLL”。问题根源很简单你的exe启动时需要去加载这些动态库但系统不知道去哪找。常见的解决方案是给系统PATH环境变量加上三个路径OSG安装目录的bin目录存放osg核心DLLOSGEarth安装目录的bin目录3rdParty根目录下的bin目录存放GDAL、curl等DLL加完PATH后记得重启Visual Studio再跑程序因为VS不会动态刷新环境变量。注意正式发布程序时别依赖PATH而是把需要DLL直接拷贝到exe同目录或者用Qt等框架的windeployqt插件机制打包。开发阶段用PATH省事发布阶段要干净。4.3 GDAL/插件版本冲突这个坑最隐蔽。程序启动时提示“无法定位程序输入点于GDAL.dll”或者加载数据时提示“找不到驱动程序”很可能是因为系统PATH里还残留着另一个版本的GDAL常见的就是QGIS、ArcGIS或Python环境自带的GDAL。OSGEarth用的是它自己编译的GDAL版本如果你的程序运行时先找到了另一个版本的GDAL两个版本的符号表不一致直接崩溃或功能异常。解决办法把3rdParty的bin目录在系统PATH里置于最靠前的位置确保它排在QGIS、Python等目录前面。如果还不行先进“系统环境变量”里把多余的GDAL路径暂时移除排查出具体是哪个软件干扰的。下面是我常用的排查速查表问题现象可能原因解决方式CMake找不到GDAL/curl/GEOS3rdParty变量未设置或路径不对检查3RD_PARTY_DIR/ACTUAL_3RDPARTY_DIR变量值链接报_ITERATOR_DEBUG_LEVEL冲突Debug/Release库混用严格匹配debug和release版本的lib链接报__imp___iob_func运行库方式不一致统一使用/MD动态运行库exe启动找不到DLL系统PATH缺少3rdParty/bin把bin目录加入PATH并重启VS启动报GDAL程序输入点错误系统里有多个GDAL版本将3rdParty/bin放PATH最前或临时移除其他GDALOSGEarth打不开SHP/TIFGDAL插件目录被干扰检查GDAL_DRIVER_PATH或环境变量5. 实战扩展如何判断一个点是否落在FeatureNode内5.1 需求场景与两种实现思路环境搭好之后很多人会开始做业务功能。最近收到不少类似的问题在三维场景里点击地图怎么判断点落在了哪个行政区、哪个地块内部这就是“判断点是否在FeatureNode内”的典型场景。实现思路通常有两种第一种是几何语义判断拿到FeatureNode里保存的矢量要素Feature把点坐标转换到要素的坐标系然后用几何库的Contains关系做判断。这种方式语义准确适合“点在多边形内部”的判断。第二种是射线求交从视点发出一条射线用osgUtil::IntersectionVisitor去和场景求交命中的节点如果是FeatureNode说明射线穿过了几何体。但这种方式只适合“点撞到了三角形面片”的场景不适合判断点是否在面内部因为三角形网格内部的空洞、边界都可能影响判断。所以很自然地我们应该用第一种方式来实现真正的“点在面内”判断。5.2 基于Feature几何的Contains判断实现核心思路分四步先拿到FeatureNode再取到它管理的Feature接着把查询点转换到Feature所在坐标系最后调用OGR几何的Contains方法。以osgEarth::Features::FeatureNode为例下面是一个最小实现框架#include osgEarthFeatures/FeatureNode #include osgEarth/GeoPoint bool pointInFeatureNode(osg::Vec3d worldPoint, osgEarth::Features::FeatureNode* node) { if (!node) return false; // 1. 获取Feature。注意一个FeatureNode可能管理多个Feature // 这里先处理单个Feature的常见情况。 osg::ref_ptrosgEarth::Features::Feature feature node-getFeature(); if (!feature.valid()) return false; // 2. 获取Feature自身的SRS空间参考系 const osgEarth::SpatialReference* featureSRS feature-getSRS(); if (!featureSRS) return false; // 3. 将你的世界坐标点转换到Feature所在的SRS。 // 如果你拿到的本来就是经纬度坐标直接构造GeoPoint即可 // 如果是viewer世界坐标需要先从MapNode的mapSRS做一次转换。 osgEarth::GeoPoint worldGeo( osgEarth::SpatialReference::get(wgs84), worldPoint.x(), worldPoint.y(), worldPoint.z()); osgEarth::GeoPoint localPoint; worldGeo.transform(featureSRS, localPoint); // 4. 使用OGR Geometry的Contains做空间判断 OGRGeometry* geom feature-getGeometry()-getGeometry(); if (!geom) return false; // 构造待判断点 OGRPoint point(localPoint.x(), localPoint.y()); return geom-Contains(point) TRUE; }这段代码省略了SRS参数动态获取的处理。实际项目里如果你的场景是一个MapNode更严谨的做法是const osgEarth::SpatialReference* mapSRS mapNode-getMapSRS(); osgEarth::GeoPoint worldGeo(mapSRS, worldPoint.x(), worldPoint.y(), worldPoint.z());然后投射到Feature的SRS。直接写死wgs84在多数项目里能用但如果地图投影不是经纬度结果会偏移。5.3 坐标转换与性能注意点坐标转换是整个判断里最容易出错的地方。三维世界里世界坐标、经纬度、投影坐标、局部坐标每个坐标系的定义都有细微差别。只要SRS搞错Contains结果就是错的而且还很难一眼看出问题。我建议的做法是先用一个已知多边形和几个已知落点做单元测试验证坐标转换正确后再接入正式逻辑。比如在一个100米边长的正方形地块中心加一个点周围相隔几米多测几个点观察Contains结果是否符合预期。性能方面如果FeatureNode里只有一个Feature直接调用Contains没有压力。但如果你面对的是几百个、上千个Feature节点比如全国行政区划的Feature节点集合逐一遍历所有Feature做Contains就会很慢。这时候可以考虑两个优化方向先用Feature的包围盒Extent粗筛只有当点落在包围盒内时才进入精确Contains判断。使用FeatureSourceIndex或者自定义空间索引提前把Feature按区域划分减少遍历范围。如果你的需求是“点击图层判断选中哪个Feature”还可以考虑用osgEarth的FeatureSourceIndex去反向索引效率会高很多。另外注意点要素和线要素不能用Contains。点没有面积概念线没有内部区域概念。如果要判断点和线的距离应该用OGR的Distance方法设定一个阈值比如“点离线的距离小于10米算命中”。这个细节很多人会漏掉。说实话我这套环境从第一次编译到跑通反复折腾了一周后来换了官方预编译3rdParty包半小时就把原本卡了好几天的编译问题解决了。最后再分享一个小技巧如果遇到“之前还能编译过几天突然不行了”的情况不用急着重装环境先检查系统PATH里是不是装了什么新软件带了另一个版本的GDAL进来。这个问题我碰到过两次都是因为QGIS和Python环境“截胡”了DLL。环境稳定之后剩下的精力就可以真正花在业务开发上了。本文还有配套的精品资源点击获取
返回列表