
拿到“2.boston.earth”这个文件名时很多刚接触osgEarth的朋友会以为它只是某个三维场景的启动配置点开就完事了。但实际上这个案例文件背后是一条完整的链路从shapefile矢量数据的读取、坐标参考系的换算、符号规则的编写到最终把建筑轮廓、道路中心线这些二维数据“立起来”变成三维模型。这篇文章就拿boston这个案例当引子把osgEarth里矢量转三维模型这件事从头到尾讲透让你看完自己也能写出来。内容适合三类人一是刚上手osgEarth、想搞明白earth文件到底怎么写的初学者二是手里有arcgis导出的shapefile数据、正愁怎么在三维场景里可视化展示的GIS开发三是想给项目做技术选型、判断osgEarth能不能承接“矢量数据批量建模”需求的人。全程按实际踩坑的经历来讲不绕弯子。1. 2.boston.earth到底是什么案例背后的技术闭环1.1 从文件名拆解earth文件在osgEarth里的角色osgEarth是一个基于OpenSceneGraph的三维地形渲染引擎它的一个核心设计思路是“用文本描述场景”。所谓earth文件本质上就是一个XML格式的场景描述文档文件里定义了加载哪些影像、哪些高程、哪些矢量数据、如何符号化、使用哪种坐标参考系。osgEarth启动后读取这个文件按照里面的声明去调度数据最终渲染出一个三维地球或局部地形场景。“2.boston.earth”这个命名其实是沿用了osgEarth早期示例的习惯“2”代表这是面向osgEarth 2.x接口的示例文件boston表示场景区域是美国波士顿。里面通常包含三类图层影像层提供底图纹理高程层提供起伏地形模型层或矢量层负责把建筑、道路等shapefile数据加载进来。波士顿这个区域的案例在GIS圈里很常见因为它有公开的建筑轮廓数据和相对规整的街道网络非常适合演示矢量拉伸建模。1.2 从shapefile到三维模型必须跨过的三道坎直接用shapefile生成三维模型看起来就是把二维多边形加一个高度值但实际操作中要跨过三道坎。第一道坎是坐标参考系。shapefile通常是平面投影坐标比如UTM或者各地方坐标系而osgEarth场景默认跑在WGS84经纬度或者地心坐标系里两套坐标系不统一数据加载进来就会飞出地球或者堆在原点。第二道坎是“转成什么”的问题。矢量数据本身只是数学意义上的点线面在三维场景里可以做成贴在地面上的彩色多边形也可以沿垂直方向拉伸成体块还可以用别的三维模型文件去做实例替换。第三道坎是符号化参数的映射。shapefile属性表里的字段比如建筑层数、高度、名称、类型需要想办法映射到三维渲染的视觉参数上这就要靠style表达式来解决。boston这个案例最有价值的一点就是它把这三道坎都演示了一遍影像数据如何配准、建筑shp如何按属性拉伸、道路和地块如何贴地显示。把这个案例拆开看懂了其它场景只是换个数据源的事。2. shapefile进入osgEarth之前数据准备与坐标系校验2.1 shapefile文件家族不止.shp一个文件在arcgis里导出一份shapefile你会看到同名的一堆文件常见的有.shp、.shx、.dbf、.prj、.sbn、.sbx等。很多新手只把.shp文件拷走结果加载时报错其实就是文件不完整。.shp存几何图形.shx是索引.dbf是属性表这三者是基础三件套缺一个都打不开。.prj存坐标系描述这个文件太容易被忽略了但恰恰是它决定了osgEarth能不能正确解析数据位置。我给一个建议在做任何三维可视化之前先用QGIS或者arcmap把shapefile打开看一眼确认几何类型点、线、面、属性字段和坐标系。尤其是确认要素是Polygon还是MultiPolygon这会影响后面拉伸建模的配置方式。属性表里的高度字段如果是字符串类型在后面写expression时会被当成文本处理导致拉伸失效这也是常见坑。2.2 坐标系校验为什么矢量数据飞到了非洲海岸osgEarth里坐标参考系的基础配置在map节点上。如果你写的是typegeocentric那是地心坐标系单位是米一个经纬度坐标可能直接被解释成地心直角坐标数据位置自然完全错乱。如果写的是typegeodetic则是经纬度坐标但如果shapefile本身是投影坐标直接加载也会错位。判断一个shapefile的坐标系最直接的方式是用GDAL的命令行工具gdalinfo它会读取.prj内容并输出一个完整的WKT描述。对于没有.prj文件的shapefile需要先根据数据来源确定投影参数比如数据是某城市规划局提供的通常是当地的城市坐标系或者国家2000投影坐标系这种情况下需要先通过arcgis的投影工具转换到WGS84再交给osgEarth使用。2.3 高度与单位陷阱建筑高度字段不能盲目使用shapefile属性表里的高度字段单位不一定是米。有的数据源用的是英尺foot波士顿的数据尤其容易出现这个问题因为美国很多公开建筑数据习惯用英尺记录高度。在配置拉伸高度时如果直接把这个值当米用你会发现建筑高度变成实际的三倍多整个城市跟积木堆起来一样。解决办法很简单先看属性表里字段的备注说明如果单位是英尺就在expression里乘以0.3048换算成米。同样地很多属性表里只有建筑层数而没有建筑高度这时候需要估算层高普通住宅按3米每层写字楼按3.8米到4.2米每层也能得到一个能用于演示的效果。3. 写出可用的earth文件矢量拉伸三维模型关键配置拆解3.1 earth文件整体骨架map节点与options先给一个我实际验证过可以跑的earth文件骨架这个文件逻辑完整不像默认模板那么简陋。假设本地有一个boston_buildings.shp和一个dem.tif用这个配置可以直接把建筑拉伸成三维体块。map nameboston_demo typegeocentric version2 options lightingfalse/lighting /options image layer drivergdal nameboston_image url./data/boston_imagery.tif/url /image elevation layer drivergdal nameboston_dem url./data/boston_dem.tif/url /elevation model layer driverfeature_model nameboston_buildings features driverogr url./data/boston_buildings.shp/url /features styles style typetext/css building { extrusion: height; extrusion-height: [height_m]; extrusion-flatten: true; fill-color: #b0b0b0; stroke-color: #404040; stroke-width: 1.5; } /style /styles /model /map这个配置里image layer负责底图elevation layer负责地形起伏model layer就是矢量建模的核心。注意model layer的driver是feature_model意思是“把矢量要素渲染成几何模型”。features子节点里的driverogr告诉osgEarth用GDAL的OGR接口去读shapefile这是最常用的方式比单独写drivershp要通用因为它还能读GeoJSON、PostGIS等。3.2 矢量样式语言CSS风格的规则系统osgEarth的矢量样式语言参考了CSS的写法核心是把要素的视觉表现拆成一个个规则。上面的例子里building就是样式选择器表示这个规则应用于名称为building的要素。你也可以用属性过滤比如写[height_m30]作为选择器只拉伸高度大于30米的建筑。关键属性有三个。第一个是extrusion它决定了拉伸方式常用的值是height意思是“按要素属性值沿垂直方向拉伸”。第二个是extrusion-height指定具体的拉伸高度值或表达式这里我写的是[height_m]表示读取属性字段height_m的值。第三个是extrusion-flatten这个很多人不知道它表示是否在拉伸前把多边形压平默认值是false如果地形起伏大不压平的话建筑的底面会随着地形扭曲所以建筑通常要设成true。3.3 表达式与属性映射让数据驱动模型形态如果你只是把所有建筑都拉伸成同样的灰色方块那这个三维模型没有太多信息量。更好的做法是通过表达式把属性表中的信息映射成颜色、高度、透明度等视觉变量。举个例子把建筑按高度染色可以用下面这种样式配置style typetext/css building { extrusion: height; extrusion-height: [height_m]; fill-color: expression(color uvec3(1.0 - [height_m]/100.0, [height_m]/100.0, 0.4)); } /styleexpression支持osgEarth内置的表达式语法这里uvec3表示一个三维向量用来构造RGB颜色。这个表达式的意思是高度越高红色分量越低绿色分量越高形成一个从红到绿的渐变。这种把属性映射到视觉变量的方式在数据可视化里非常实用比如用颜色区分地类、用高度表示人口密度都能直接在样式里写完。3.4 拉伸高度单位与地形基准让建筑稳稳站在地面上在geocentric场景里拉伸高度默认的单位是米但这只是“相对高度”建筑的底部在哪个高程基准上则取决于是否开启clamp。如果建筑数据本身没有高程信息只靠拉伸是无法贴合地形的底部可能陷入地里或者悬空。地形基准的处理有三种常见模式。一是靠elevation layer提供真实地形然后让建筑底部贴到地形表面也就是clamp到地形二是完全忽略地形把建筑底面放在海拔0米适合平坦场地或飞行漫游场景三是用extrusion-flatten把压平再用extrusion-height作相对高度让建筑随着地形整体起伏。实际项目里城市级场景通常选第一种但要注意建筑密度高的时候逐要素clamp会导致布置和地形采样计算量成倍增长大数据量时需要考虑性能优化。3.5 点、线、面不同几何类型的建模思路shapefile不只是面要素道路是线要素路灯、树木是点要素它们在osgEarth里的建模方式完全不同。面要素最适合拉伸成体块这是最直观的三维模型生成方式。线要素一般做贴地处理或管线建模比如道路可以贴在地形表面形成路网也可以用extrusion做墙体式效果或者是做管线时把线偏移一个半径变成圆柱。点要素适合做模型替换也就是所谓的instance给每个点位置放一个gltf模型文件。模型替换的配置方法是在样式里指定模型源比如model layer driverfeature_model nametrees features driverogr url./data/trees.shp/url /features styles style typetext/css tree { model: ./models/oak.gltf; scale: 1.0; } /style /styles /model这种部署方式的优势在于几十万个点要素加载的是同一个模型文件GPU实例化渲染不会因为模型数量多而卡顿。4. 实操过程与核心环节实现从零搭一个波士顿街区4.1 数据准备环节公开数据的获取与预处理要复现这个案例第一步是准备数据。波士顿地区的建筑轮廓数据可以从MassGIS马萨诸塞州地理信息平台下载数据格式是shapefile坐标系通常已经是WGS84或者Massachusetts State Plane投影。如果是State Plane投影需要用arcgis或QGIS先做一个投影转换。我的习惯是先建一个固定目录结构把原始数据放data_raw处理后的数据放dataearth文件和模型文件放根目录下。整个流程里最影响成败的是两个操作一是统一坐标系在QGIS里把原始shp另存为WGS84地理坐标系二是用窗口裁剪数据范围避免加载整个州的数据导致场景卡顿。用一个矩形框切出波士顿市中心范围一般就能满足演示需求。4.2 高程数据准备没有DEM时让建筑看起来更真实很多演示案例只做建筑拉伸没有高程数据出来的效果就像一堆盒子平铺在平地上虽然能用但少了地面的起伏感。加一层高程数据之后地形的不规则起伏配合建筑拉伸立体感立刻强很多。DEM数据可以用SRTM或者ASTER GDEM的公开数据下载范围覆盖全球分辨率在30米左右做城市级场景基本够用。下载下来的是tif格式直接给osgEarth读就行。要注意的是SRTM原始数据可能带了NoData值加载到osgEarth里会出现坑洞建议在QGIS里先用填洼工具或者重新采样把NoData处理掉再导出成GeoTIFF。4.3 编写earth配置文件从空文件到完整场景实际写earth文件时我习惯分三步走。第一步只写map节点和影像层加载后确认底图位置正确经纬度对得上。第二步加高程层看地形起伏是否正常。第三步加vector model图层逐个检查矢量数据的加载效果。在第一步确认位置的时候可以直接用osgEarth附带的osgearth_qt工具打开earth文件鼠标左键旋转、滚轮缩放判断相机视角是否正确。如果影像层加载完场景一片白或黑多半是影像数据范围与map节点的坐标系不匹配或者数据源URL路径写错了。第三步里最容易出问题的是style选择器名称的匹配。如果在shapefile的属性表里有一个字段叫NAME值是BuildingA那么在CSS里可以写[NAMEBuildingA]作为精确匹配选择器。很多时候模型不显示就是因为选择器名称和属性值对不上。4.4 进阶优化多密度LOD与批量瓦片当数据量变大时直接把几十万个要素全部拉伸成模型无论内存还是帧率都顶不住。解决方案是用osgEarth的LOD机制对矢量数据进行分层在远距离显示简化版近距离显示完整模型。我用的一个比较实用的策略是把矢量图层拆成两个model layer一个加载经过多边形简化算法处理过的低精度shp用于远距离显示另一个加载原始高精度shp用于近距离显示。然后通过max_range和min_range属性控制显示距离。model layer driverfeature_model namebuildings_lod2 features driverogr url./data/boston_buildings_simplify.shp/url /features max_range3000/max_range styles style typetext/css building { extrusion: height; extrusion-height: [height_m]; fill-color: #909090; } /style /styles /model model layer driverfeature_model namebuildings_lod1 features driverogr url./data/boston_buildings.shp/url /features min_range0/min_range max_range1500/max_range styles style typetext/css building { extrusion: height; extrusion-height: [height_m]; fill-color: #b0b0b0; } /style /styles /model这样的分层配置不会增加地图文件复杂度但场景流畅度提升非常明显算是投入产出比最高的一种优化手段。4.5 在场景中叠加注记和辅助要素除了三维模型形状数据还能用来生成注记。osgEarth的annotation功能可以把点、线、面的属性文本直接贴到场景里。例如给建筑加一个名称标签配置方式是在model layer之外再加一个annotation layer。annotation layer driverogr namelabels features driverogr url./data/boston_buildings_labels.shp/url /features styles style typetext/css text { text: [NAME]; fill-color: #ffffff; declutter: true; } /style /styles /annotationdeclutter属性很实用它开启文字避让密集的建筑不会出现标签互相遮挡的情况。不过在三维场景里标签的朝向和大小需要跟着视角实时调整这个设置在osgEarth 2.x里面还不够灵活很多项目会选择在渲染后处理阶段或者用UI组件叠加而不是直接用annotation。5. 常见问题与排查技巧实录按报错场景逐一解决5.1 矢量数据加载不上OGR读取失败的原因报错信息一般在控制台输出常见的有“Unable to open datasource”和“Failed to open shapefile”。前者要检查路径、文件名拼写尤其是Windows系统下路径里的反斜杠要改成双反斜杠或正斜杠。后者要检查三件套文件是否齐全尤其是.dbf缺失会导致属性读取失败。还有一个非常隐蔽的问题是shapefile文件名包含了中文或特殊字符。GDAL/OGR对文件路径编码的处理在不同操作系统上并不一致遇到中文路径容易直接打不开。我的经验是项目路径尽量用纯英文数据文件名也规范成字母加下划线的格式。5.2 模型显示不出来样式规则没有命中配置写了一大段结果场景里一个模型都没有这种情况八成是style选择器没有匹配到任何要素。osgEarth的样式规则是按顺序匹配的第一个匹配的规则生效后面同名规则不会覆盖。排查步骤是先去掉样式选择器里的属性过滤写一个通配规则比如“*”确认要素本身的几何图形能不能加载如果能加载再逐步加回属性过滤条件。还有一个辅助手段在命令行里加上OSGEARTH_NOTIFY_LEVELDEBUG能看到每条矢量要素被哪些样式规则处理。5.3 模型整体偏移坐标系基准不一致整体偏移的特征是模型不在影像对应的位置上而是整体移动了一段距离。通常原因是shapefile是投影坐标但没有被转成WGS84或者map节点用了geocentric而数据是经纬度。解决思路是用gdaltransform或者QGIS重新投影而不是在osgEarth里手动加偏移。手动加偏移只能平移一个固定量但只要数据范围大一点或者地形有旋转正确转投影之后才不会有形变误差。对于精度要求高的场景比如城市规划项目建议用七参数转换不能简单用WGS84的经纬度直接替换。5.4 建筑悬浮或陷地高程基准和拉伸基准不一致这个问题在城市级项目中特别明显尤其是在起伏地形上。建筑浮在半空是因为拉伸后的体块底部还是参照海拔0米而地形本身海拔已经几十米了。解决办法是给拉伸的样式开启clamp让几何体统一贴到地形表面。配置里需要在style块中加一行building { clamp-to-terrain: true; }如果用了clamp-to-terrain建筑依然陷地常见原因是DEM数据本身精度不够在陡峭地形上的采样点高程有偏差导致建筑被嵌入坡面。这时候可以配合extrusion-flatten让建筑底面纯水平再把底部做一个小幅度的抬高视觉上就稳了。5.5 大数据量场景卡顿性能优化方向当要素数量超过十万级而且每个要素都带拉伸帧率会明显下降。性能瓶颈主要在几何体生成和渲染提交两个环节。优化方向有三个。第一个是简化几何在QGIS或者arcgis里用简化算法减少多边形顶点数对建筑这种规则轮廓顶点数减一半几乎看不出差别。第二个是分段显示前面提到的LOD方式远距离用低精度数据甚至用图片替代。第三个是开启缓存和批处理osgEarth支持在加载时对矢量进行瓦片化生成缓存到本地第二次运行时速度会有数量级提升配置方式是加一个缓存目录map options cache typefilesystem path./cache/path /cache /options /map第四个方向是把静态建筑批量导出成b3dm或者gltf瓦片交给专门的瓦片加载模块这个适合做最终交付因为运行时不需要再读shp、再执行样式解析而是直接加载已烘焙好的三维模型瓦片性能是最佳的。写在最后一点个人经验我在实际项目里用osgEarth处理过不少类似boston的建筑矢量数据最大的体会是这个框架把“数据到模型的转换”压缩到了配置层面你不需要写复杂的C代码但你对坐标、单位、样式属性映射的理解必须足够扎实否则一个很小的单位换算错误整个城市模型的高度和位置就会全线崩溃。所以如果你刚开始学建议别急着追求炫酷效果先从一份简单的建筑shp跑通整个流程再去叠加复杂属性、做色彩映射和LOD优化。等你把这一套跑顺了从shapefile到三维模型的整个链路就彻底成为你自己的东西了。