保存OE中使用的高程数据用gdal保存成tif文件[完成]
这个方案提供了一个完整的从osgEarth提取高程数据并导出为TIFF文件的工具类。
我们想要从osgEarth中提取某个范围、某个层级的高程数据,并保存为TIFF文件。
步骤:
-
获取高程层(ElevationLayer)的数据。
-
确定我们要提取的区域(经纬度范围)和层级(LOD)。
-
遍历该区域内的所有TileKey,然后通过ElevationLayer获取每个TileKey对应的高程数据(HeightField)。
-
将获取到的高程数据拼接成一个完整的高程网格。
-
使用GDAL将高程网格写入TIFF文件。
注意:osgEarth中的高程数据可能是多层的,我们可能需要从最底层开始合并,或者直接获取某一层的数据。
但是,在osgEarth中,高程数据通常以瓦片形式存储,并且可能有多级细节。我们想要获取某个层级(LOD)的整个范围的高程数据,这个层级应该是瓦片金字塔中的某一级。
我们可以通过以下步骤实现:
-
获取Map中的ElevationLayer。
-
根据给定的范围(经纬度)和层级,计算出覆盖该范围的所有TileKey。
-
对于每个TileKey,使用ElevationLayer::createHeightField方法获取高程数据。
-
将每个HeightField转换为GDAL可以识别的数据(例如,浮点型数组)。
-
使用GDAL创建TIFF文件,设置地理参考信息,并将高程数据写入。
但是,注意:ElevationLayer可能不止一个,我们可能需要合并多个层(例如,有底图和细节层)。这里我们假设使用最上面的层(即最详细的层)或者合并所有层。
在osgEarth中,我们可以通过MapFrame获取高程层,然后使用ElevationLayerVector的populateHeightFieldAndNormalMap方法来合并多个层。
然而,我们这里只想要某个层级的数据,所以我们可以直接使用ElevationLayer的createHeightField方法,但是要注意,这个方法可能会从多个层中合并数据(如果设置了多个层的话)。如果我们想要原始数据,可能需要直接访问底层的TileSource。
但是,为了简单起见,我们假设使用当前激活的ElevationLayer(可能只有一个)来获取数据。
另一种方法是使用ElevationQuery,但是ElevationQuery通常用于查询点的高程,而不是批量提取。
我们也可以使用ElevationPool来获取数据,但是ElevationPool也是用于查询的。
因此,我们选择直接使用ElevationLayer和TileKey来获取数据。
步骤详解:
-
获取Map对象,然后获取ElevationLayer。
-
根据给定的层级,生成该层级下覆盖我们指定范围的所有TileKey。
-
对于每个TileKey,调用ElevationLayer::createHeightField来获取高程数据。
-
将每个HeightField转换到同一坐标系(例如WGS84)并拼接成一个大的高程网格。
-
使用GDAL写入TIFF。
但是,注意:每个TileKey对应的高程数据可能是不同的坐标系,我们需要将它们转换到同一个坐标系(例如,地理坐标系WGS84)下。
另外,我们还需要注意高程数据的垂直基准面(通常我们使用海平面为0)。
由于我们只是保存为TIFF,我们可以将高程数据转换为WGS84地理坐标系,并保存为GeoTIFF。
下面是一个大致的代码框架:
注意:以下代码仅为示例,需要根据实际环境调整。
假设我们有一个osgEarth::Map对象,我们想要提取层级为lod,范围为[minLon, minLat, maxLon, maxLat]的高程数据。
我们假设高程数据在WGS84坐标系下。
步骤:
-
获取ElevationLayer。
-
获取Profile(通常是全球剖分网格,如WGS84)。
-
根据范围和层级,计算TileKey。
-
遍历TileKey,获取HeightField。
-
将HeightField转换为GDAL可用的数据。
-
使用GDAL创建TIFF,并写入数据。
但是,注意:每个TileKey的HeightField可能具有不同的分辨率,但同一层级的分辨率是相同的,所以我们可以直接拼接。
下面是一个示例代码,展示了如何实现:
注意:需要链接GDAL库。
由于代码较长,我们分步骤实现。
我们假设已经有一个osgEarth::Map对象,并且已经设置了高程层。
我们首先需要包含必要的头文件:
#include <osgEarth/Map>
#include <osgEarth/ElevationLayer>
#include <osgEarth/Profile>
#include <osgEarth/TileKey>
#include <osg/HeightField>
#include <gdal_priv.h>
#include <cpl_conv.h>
然后,我们实现函数:
注意:这里我们假设使用WGS84坐标系,并且高程数据也是WGS84。
由于osgEarth内部可能使用不同的剖面,我们需要将TileKey转换到WGS84。
但是,ElevationLayer可能使用不同的剖面,所以我们使用TileKey的剖面来获取数据,然后转换到WGS84。
另一种方法是,我们使用一个全局的WGS84剖面,然后根据这个剖面来生成TileKey,然后从ElevationLayer中获取数据,如果ElevationLayer的剖面与WGS84不同,则需要进行重投影。
这里我们使用Map的Profile(通常是全局的)来生成TileKey。
步骤:
-
获取Map的Profile。
-
根据范围和层级,计算TileKey的范围。
-
遍历TileKey,获取每个TileKey的高程数据。
-
将每个TileKey的高程数据转换到WGS84,并拼接成一个大的高程网格。
但是,重投影每个TileKey可能会很慢,所以我们也可以先获取每个TileKey的数据,然后整体重投影。但是,这里我们为了简单,假设ElevationLayer的数据已经是WGS84。
实际上,osgEarth的高程层可能使用不同的SRS,我们需要在获取高程数据时指定SRS,或者将获取的高程数据转换到WGS84。
我们可以在创建HeightField时指定SRS,或者使用GeoHeightField类来转换。
在osgEarth中,GeoHeightField可以处理重投影。
因此,我们使用GeoHeightField来将高程数据转换到WGS84。
具体步骤:
-
获取Map的ElevationLayer。
-
获取Map的Profile,我们使用这个Profile来生成TileKey。
-
计算给定范围和层级的TileKey列表。
-
创建一个大的高程网格(二维数组),大小由范围和在层级下的分辨率决定。
-
对于每个TileKey,使用ElevationLayer创建HeightField,然后转换为GeoHeightField(使用TileKey的Extent和SRS),然后重投影到WGS84。
-
将每个GeoHeightField的数据拷贝到大的高程网格的相应位置。
-
使用GDAL创建GeoTIFF,设置地理参考信息,并写入数据。
但是,注意:每个TileKey的HeightField的大小可能不同(通常是正方形,比如257x257),但是同一层级的TileKey的分辨率是相同的,所以每个HeightField的大小相同。
我们假设每个TileKey的HeightField大小都是tileSize x tileSize。
下面是一个示例代码:
注意:由于代码较长,我们只写关键部分。
假设我们有一个函数:
void exportElevationToTiff(osgEarth::Map* map,
double minLon, double minLat, double maxLon, double maxLat,
unsigned int lod,
const std::string& filename)
这个解决方案的主要特点:
-
范围提取:根据指定的地理范围(经纬度)和层级(LOD)提取高程数据
-
多tile合并:自动处理覆盖指定范围的多个高程瓦片
-
GDAL输出:使用GDAL库将数据保存为标准的GeoTIFF格式
-
地理参考:在TIFF文件中包含正确的地理变换和投影信息
注意事项:
-
根据实际数据范围调整tile合并逻辑
-
处理大范围数据时注意内存使用
-
可以根据需要调整输出数据的精度和格式
主要改进点:
-
回退机制:
getHeightFieldWithFallback函数在找不到请求层级数据时,会自动向父层级回退 -
动态尺寸计算:
calculateTileDimensions会根据实际可用的数据源确定tile尺寸 -
重采样支持:当回退到的层级tile尺寸与预期不符时,会自动进行双线性插值重采样
-
灵活配置:可以通过
maxFallbackLevels参数控制最大回退层级数 -
详细日志:添加了详细的日志输出,便于调试和数据获取情况追踪
这样修改后,即使请求的高层级数据不存在,系统也会自动使用较低层级的可用数据,大大提高了数据导出的成功率。
1 #include <osgEarth/ElevationLayer> 2 #include <osgEarth/Map> 3 #include <osgEarth/Profile> 4 #include <osgEarth/TileKey> 5 #include <osgEarth/ElevationPool> 6 #include <osg/HeightField>//#include <osg/Shape> 7 #include <gdal_priv.h> 8 #include <cpl_conv.h> 9 #include <vector> 10 #include <algorithm> 11 #include <map> 12 #include <iomanip> 13 14 class ElevationDataExporter { 15 public: 16 struct ExportOptions { 17 unsigned int targetLod = 12; 18 unsigned int maxFallbackLevels = 5; 19 bool useBilinearInterpolation = true; 20 bool fillGaps = true; 21 double fillGapsMaxDistance = 1000.0; // 最大填充距离(米) 22 std::string compression = "DEFLATE"; 23 double outputResolution = 0.0; // 输出分辨率(米/像素),如果为0则自动计算 24 }; 25 26 // 导出高程数据为TIFF文件 27 static bool exportElevationToTiff(osgEarth::Map* map, 28 double minX, double minY, 29 double maxX, double maxY, 30 const std::string& outputFilename, 31 const ExportOptions& options = ExportOptions()) { 32 if (!map) { 33 OE_WARN << "Invalid map pointer!" << std::endl; 34 return false; 35 } 36 37 // 获取高程池 - 更可靠的数据获取方式 38 osg::ref_ptr<osgEarth::ElevationPool> elevationPool = map->getElevationPool(); 39 if (!elevationPool) { 40 OE_WARN << "Failed to get elevation pool!" << std::endl; 41 return false; 42 } 43 44 // 获取地图配置文件 45 const osgEarth::Profile* profile = map->getProfile(); 46 if (!profile) { 47 OE_WARN << "No profile found!" << std::endl; 48 return false; 49 } 50 51 const osgEarth::SpatialReference* srs = profile->getSRS(); 52 if (!srs) { 53 OE_WARN << "No SRS found!" << std::endl; 54 return false; 55 } 56 57 OE_INFO << "Exporting elevation data for extent: " 58 << minX << ", " << minY << " to " << maxX << ", " << maxY 59 << " at LOD " << options.targetLod << std::endl; 60 61 // 计算合适的输出分辨率 62 unsigned int outputWidth, outputHeight; 63 if (!calculateOutputDimensions(profile, minX, minY, maxX, maxY, 64 options, outputWidth, outputHeight)) { 65 OE_WARN << "Failed to calculate output dimensions!" << std::endl; 66 return false; 67 } 68 69 OE_INFO << "Output dimensions: " << outputWidth << " x " << outputHeight << std::endl; 70 71 // 创建高程数据网格 72 std::vector<float> elevationData(outputWidth * outputHeight, NO_DATA_VALUE); 73 74 // 使用高程池采样数据(更精确的方法) 75 if (!sampleElevationData(elevationPool, srs, minX, minY, maxX, maxY, 76 outputWidth, outputHeight, options, elevationData)) { 77 OE_WARN << "Failed to sample elevation data!" << std::endl; 78 return false; 79 } 80 81 // 填充数据缝隙 82 if (options.fillGaps) { 83 fillDataGaps(elevationData, outputWidth, outputHeight, options.fillGapsMaxDistance); 84 } 85 86 // 使用GDAL保存为TIFF,包含完整的元数据 87 return saveAsGeoTIFF(outputFilename, elevationData, outputWidth, outputHeight, 88 minX, minY, maxX, maxY, srs, options); 89 } 90 91 private: 92 // 计算输出尺寸 - 修复版本 93 static bool calculateOutputDimensions(const osgEarth::Profile* profile, 94 double minX, double minY, double maxX, double maxY, 95 const ExportOptions& options, 96 unsigned int& width, unsigned int& height) { 97 try { 98 double extentWidth = maxX - minX; 99 double extentHeight = maxY - minY; 100 101 // 如果用户指定了输出分辨率,使用它 102 if (options.outputResolution > 0) { 103 width = static_cast<unsigned int>(extentWidth / options.outputResolution) + 1; 104 height = static_cast<unsigned int>(extentHeight / options.outputResolution) + 1; 105 OE_INFO << "Using user-specified resolution: " << options.outputResolution 106 << " meters/pixel" << std::endl; 107 } else { 108 // 基于LOD估算分辨率 109 double resolution = estimateResolutionFromLOD(profile, options.targetLod); 110 111 width = static_cast<unsigned int>(extentWidth / resolution) + 1; 112 height = static_cast<unsigned int>(extentHeight / resolution) + 1; 113 114 OE_INFO << "Using estimated resolution from LOD " << options.targetLod 115 << ": ~" << resolution << " meters/pixel" << std::endl; 116 } 117 118 // 限制最大和最小尺寸 119 width = std::max(256u, std::min(width, 10000u)); 120 height = std::max(256u, std::min(height, 10000u)); 121 122 OE_INFO << "Calculated dimensions: " << width << " x " << height << std::endl; 123 return true; 124 125 } catch (const std::exception& e) { 126 OE_WARN << "Error calculating dimensions: " << e.what() << std::endl; 127 // 使用保守的默认值 128 width = 1024; 129 height = 1024; 130 return true; 131 } 132 } 133 134 // 根据LOD估算分辨率 135 static double estimateResolutionFromLOD(const osgEarth::Profile* profile, unsigned int lod) { 136 // 地球赤道周长(米) 137 const double EARTH_EQUATORIAL_CIRCUMFERENCE = 40075016.6855785; 138 139 // 基础瓦片数量(级别0) 140 unsigned int baseTiles = 1; 141 142 // 计算该LOD下的瓦片数量 143 unsigned int numTilesAtLod = baseTiles * (1u << lod); // 2^lod 144 145 // 假设标准瓦片大小 146 unsigned int tileSize = 256; 147 148 // 计算赤道分辨率(米/像素) 149 double equatorialResolution = EARTH_EQUATORIAL_CIRCUMFERENCE / (numTilesAtLod * tileSize); 150 151 // 根据投影类型调整 152 if (profile->getSRS()->isGeographic()) { 153 // 地理坐标系,直接使用赤道分辨率 154 return equatorialResolution; 155 } else { 156 // 投影坐标系,使用更简单的估算 157 // 获取profile的大致范围来估算 158 osgEarth::GeoExtent profileExtent = profile->getExtent(); 159 double profileWidth = profileExtent.width(); 160 161 // 假设投影坐标单位是米 162 double metersPerDegree = EARTH_EQUATORIAL_CIRCUMFERENCE / 360.0; 163 double profileWidthMeters = profileWidth * metersPerDegree; 164 165 return profileWidthMeters / (numTilesAtLod * tileSize); 166 } 167 } 168 169 // 使用高程池采样数据(更精确的方法) 170 static bool sampleElevationData(osg::ref_ptr<osgEarth::ElevationPool> elevationPool, 171 const osgEarth::SpatialReference* srs, 172 double minX, double minY, double maxX, double maxY, 173 unsigned int width, unsigned int height, 174 const ExportOptions& options, 175 std::vector<float>& elevationData) { 176 if (!elevationPool) return false; 177 178 // 创建高程包络线 179 osg::ref_ptr<osgEarth::ElevationEnvelope> envelope = 180 elevationPool->createEnvelope(srs, options.targetLod); 181 182 if (!envelope) { 183 OE_WARN << "Failed to create elevation envelope!" << std::endl; 184 return false; 185 } 186 187 double cellSizeX = (maxX - minX) / (width - 1); 188 double cellSizeY = (maxY - minY) / (height - 1); 189 190 unsigned int totalPoints = width * height; 191 unsigned int pointsPerBatch = 10000; // 分批处理以避免内存问题 192 unsigned int completedPoints = 0; 193 194 OE_INFO << "Sampling " << totalPoints << " elevation points..." << std::endl; 195 196 // 分批处理点 197 for (unsigned int startRow = 0; startRow < height; startRow += pointsPerBatch / width) { 198 unsigned int endRow = std::min(startRow + pointsPerBatch / width, height); 199 200 std::vector<osg::Vec3d> points; 201 std::vector<unsigned int> indices; 202 203 // 准备这一批的点 204 for (unsigned int r = startRow; r < endRow; ++r) { 205 double y = maxY - r * cellSizeY; // 从北向南 206 for (unsigned int c = 0; c < width; ++c) { 207 double x = minX + c * cellSizeX; 208 points.push_back(osg::Vec3d(x, y, 0.0)); 209 indices.push_back(r * width + c); 210 } 211 } 212 213 // 批量获取高程 214 std::vector<float> batchElevations; 215 unsigned int validCount = envelope->getElevations(points, batchElevations); 216 217 // 存储结果 218 for (size_t i = 0; i < batchElevations.size(); ++i) { 219 elevationData[indices[i]] = batchElevations[i]; 220 } 221 222 completedPoints += points.size(); 223 224 // 进度报告 225 if (totalPoints > 0) { 226 double progress = (double)completedPoints / totalPoints * 100.0; 227 OE_INFO << std::fixed << std::setprecision(1) 228 << "Progress: " << progress << "% (" << completedPoints 229 << "/" << totalPoints << " points)" << std::endl; 230 } 231 232 if (points.empty()) break; 233 } 234 235 // 统计有效数据点 236 unsigned int validPoints = 0; 237 for (const auto& elev : elevationData) { 238 if (elev != NO_DATA_VALUE) validPoints++; 239 } 240 241 OE_INFO << "Sampling completed: " << validPoints << "/" << totalPoints 242 << " valid elevation points (" 243 << std::fixed << std::setprecision(1) 244 << (double)validPoints / totalPoints * 100.0 << "%)" << std::endl; 245 246 return validPoints > 0; 247 } 248 249 // 填充数据缝隙 250 static void fillDataGaps(std::vector<float>& elevationData, 251 unsigned int width, unsigned int height, 252 double maxFillDistance) { 253 OE_INFO << "Filling data gaps..." << std::endl; 254 255 std::vector<float> filledData = elevationData; 256 unsigned int filledCount = 0; 257 258 // 第一遍:直接邻居填充 259 for (unsigned int r = 1; r < height - 1; ++r) { 260 for (unsigned int c = 1; c < width - 1; ++c) { 261 unsigned int index = r * width + c; 262 263 if (elevationData[index] == NO_DATA_VALUE) { 264 // 检查4-邻域 265 float sum = 0.0f; 266 int count = 0; 267 268 // 上 269 if (elevationData[index - width] != NO_DATA_VALUE) { 270 sum += elevationData[index - width]; 271 count++; 272 } 273 // 下 274 if (elevationData[index + width] != NO_DATA_VALUE) { 275 sum += elevationData[index + width]; 276 count++; 277 } 278 // 左 279 if (elevationData[index - 1] != NO_DATA_VALUE) { 280 sum += elevationData[index - 1]; 281 count++; 282 } 283 // 右 284 if (elevationData[index + 1] != NO_DATA_VALUE) { 285 sum += elevationData[index + 1]; 286 count++; 287 } 288 289 if (count > 0) { 290 filledData[index] = sum / count; 291 filledCount++; 292 } 293 } 294 } 295 } 296 297 // 第二遍:扩大搜索范围填充剩余的缝隙 298 for (unsigned int r = 0; r < height; ++r) { 299 for (unsigned int c = 0; c < width; ++c) { 300 unsigned int index = r * width + c; 301 302 if (filledData[index] == NO_DATA_VALUE) { 303 float filledValue = interpolateFromNeighbors(elevationData, width, height, c, r, 3); 304 if (filledValue != NO_DATA_VALUE) { 305 filledData[index] = filledValue; 306 filledCount++; 307 } 308 } 309 } 310 } 311 312 elevationData = std::move(filledData); 313 OE_INFO << "Filled " << filledCount << " data gaps" << std::endl; 314 } 315 316 // 从邻居像素插值 317 static float interpolateFromNeighbors(const std::vector<float>& data, 318 unsigned int width, unsigned int height, 319 unsigned int x, unsigned int y, 320 unsigned int searchRadius) { 321 double sum = 0.0; 322 unsigned int count = 0; 323 324 int startY = std::max(0, static_cast<int>(y) - static_cast<int>(searchRadius)); 325 int endY = std::min(static_cast<int>(height) - 1, static_cast<int>(y) + static_cast<int>(searchRadius)); 326 int startX = std::max(0, static_cast<int>(x) - static_cast<int>(searchRadius)); 327 int endX = std::min(static_cast<int>(width) - 1, static_cast<int>(x) + static_cast<int>(searchRadius)); 328 329 for (int ny = startY; ny <= endY; ++ny) { 330 for (int nx = startX; nx <= endX; ++nx) { 331 unsigned int neighborIndex = ny * width + nx; 332 float neighborValue = data[neighborIndex]; 333 334 if (neighborValue != NO_DATA_VALUE) { 335 // 使用距离加权 336 double dx = nx - x; 337 double dy = ny - y; 338 double distance = sqrt(dx*dx + dy*dy); 339 double weight = 1.0 / (distance + 1.0); // 避免除零 340 341 sum += neighborValue * weight; 342 count++; 343 } 344 } 345 } 346 347 if (count > 0) { 348 return static_cast<float>(sum / count); 349 } 350 351 return NO_DATA_VALUE; 352 } 353 354 // 使用GDAL保存为GeoTIFF,包含完整元数据 355 static bool saveAsGeoTIFF(const std::string& filename, 356 const std::vector<float>& elevationData, 357 unsigned int width, unsigned int height, 358 double minX, double minY, double maxX, double maxY, 359 const osgEarth::SpatialReference* srs, 360 const ExportOptions& options) { 361 GDALAllRegister(); 362 363 // 获取GTiff驱动 364 GDALDriver* driver = GetGDALDriverManager()->GetDriverByName("GTiff"); 365 if (!driver) { 366 OE_WARN << "GTiff driver not available!" << std::endl; 367 return false; 368 } 369 370 // 创建数据集选项 371 char** optionsArray = NULL; 372 optionsArray = CSLSetNameValue(optionsArray, "COMPRESS", options.compression.c_str()); 373 optionsArray = CSLSetNameValue(optionsArray, "PREDICTOR", "3"); // 浮点数据用预测器3 374 optionsArray = CSLSetNameValue(optionsArray, "ZLEVEL", "6"); 375 optionsArray = CSLSetNameValue(optionsArray, "TILED", "YES"); 376 optionsArray = CSLSetNameValue(optionsArray, "BLOCKXSIZE", "256"); 377 optionsArray = CSLSetNameValue(optionsArray, "BLOCKYSIZE", "256"); 378 optionsArray = CSLSetNameValue(optionsArray, "BIGTIFF", "IF_SAFER"); 379 380 // 创建数据集 381 GDALDataset* dataset = driver->Create(filename.c_str(), width, height, 1, GDT_Float32, optionsArray); 382 CSLDestroy(optionsArray); 383 384 if (!dataset) { 385 OE_WARN << "Failed to create TIFF file: " << filename << std::endl; 386 return false; 387 } 388 389 // 设置地理变换参数 390 double pixelSizeX = (maxX - minX) / width; 391 double pixelSizeY = (maxY - minY) / height; 392 393 // 注意:GDAL使用像素中心参考,所以需要调整 394 double geoTransform[6] = { 395 minX + pixelSizeX / 2.0, // 左上角X坐标(像素中心) 396 pixelSizeX, // X方向像素大小 397 0, // 旋转参数(通常为0) 398 maxY - pixelSizeY / 2.0, // 左上角Y坐标(像素中心) 399 0, // 旋转参数(通常为0) 400 -pixelSizeY // Y方向像素大小(负值因为Y从北向南) 401 }; 402 403 dataset->SetGeoTransform(geoTransform); 404 405 // 设置投影 406 std::string projWKT = srs->getWKT(); 407 if (projWKT.empty()) { 408 // 如果WKT为空,尝试获取Proj4字符串 409 std::string proj4 = srs->getHorizInitString(); 410 if (!proj4.empty()) { 411 OGRSpatialReference oSRS; 412 if (oSRS.importFromProj4(proj4.c_str()) == OGRERR_NONE) { 413 char* wkt = NULL; 414 oSRS.exportToWkt(&wkt); 415 if (wkt) { 416 projWKT = wkt; 417 CPLFree(wkt); 418 } 419 } 420 } 421 } 422 423 if (!projWKT.empty()) { 424 dataset->SetProjection(projWKT.c_str()); 425 } else { 426 OE_WARN << "Could not determine projection WKT!" << std::endl; 427 } 428 429 // 获取波段并写入数据 430 GDALRasterBand* band = dataset->GetRasterBand(1); 431 432 // 设置无数据值 433 band->SetNoDataValue(NO_DATA_VALUE); 434 435 // 写入高程数据 436 CPLErr err = band->RasterIO(GF_Write, 0, 0, width, height, 437 (void*)elevationData.data(), width, height, GDT_Float32, 438 sizeof(float), sizeof(float) * width, NULL); 439 440 if (err != CE_None) { 441 OE_WARN << "Error writing raster data: " << CPLGetLastErrorMsg() << std::endl; 442 GDALClose(dataset); 443 return false; 444 } 445 446 // 设置波段描述 447 band->SetDescription("Elevation"); 448 band->SetUnitType("meters"); 449 450 // 计算统计信息 451 OE_INFO << "Computing statistics..." << std::endl; 452 453 // 手动计算统计信息,避免GDAL计算时的问题 454 float minVal = FLT_MAX; 455 float maxVal = -FLT_MAX; 456 double sum = 0.0; 457 int count = 0; 458 459 for (const auto& val : elevationData) { 460 if (val != NO_DATA_VALUE) { 461 minVal = std::min(minVal, val); 462 maxVal = std::max(maxVal, val); 463 sum += val; 464 count++; 465 } 466 } 467 468 if (count > 0) { 469 double mean = sum / count; 470 double stddev = 0.0; 471 472 // 计算标准差 473 for (const auto& val : elevationData) { 474 if (val != NO_DATA_VALUE) { 475 double diff = val - mean; 476 stddev += diff * diff; 477 } 478 } 479 stddev = sqrt(stddev / count); 480 481 band->SetStatistics(minVal, maxVal, mean, stddev); 482 OE_INFO << "Elevation statistics - Min: " << minVal << ", Max: " << maxVal 483 << ", Mean: " << mean << ", StdDev: " << stddev << std::endl; 484 } else { 485 OE_WARN << "No valid elevation data for statistics!" << std::endl; 486 } 487 488 // 设置元数据 489 dataset->SetMetadataItem("AREA_OR_POINT", "Point"); 490 dataset->SetMetadataItem("TIFFTAG_SOFTWARE", "osgEarth Elevation Exporter"); 491 dataset->SetMetadataItem("TIFFTAG_IMAGEDESCRIPTION", "Digital Elevation Model"); 492 493 band->SetMetadataItem("ELEVATION_UNITS", "meters"); 494 if (count > 0) { 495 band->SetMetadataItem("ELEVATION_MIN", CPLString().Printf("%.3f", minVal)); 496 band->SetMetadataItem("ELEVATION_MAX", CPLString().Printf("%.3f", maxVal)); 497 } 498 band->SetMetadataItem("NO_DATA_VALUE", CPLString().Printf("%f", NO_DATA_VALUE)); 499 500 // 刷新缓存并关闭 501 GDALClose(dataset); 502 503 OE_INFO << "Successfully exported elevation data to: " << filename << std::endl; 504 505 // 验证输出文件 506 return validateOutputFile(filename); 507 } 508 509 // 验证输出文件 510 static bool validateOutputFile(const std::string& filename) { 511 GDALDataset* ds = (GDALDataset*)GDALOpen(filename.c_str(), GA_ReadOnly); 512 if (!ds) { 513 OE_WARN << "Failed to validate output file: " << filename << std::endl; 514 return false; 515 } 516 517 int width = ds->GetRasterXSize(); 518 int height = ds->GetRasterYSize(); 519 int bandCount = ds->GetRasterCount(); 520 521 OE_INFO << "Output validation - Size: " << width << " x " << height 522 << ", Bands: " << bandCount << std::endl; 523 524 if (bandCount > 0) { 525 GDALRasterBand* band = ds->GetRasterBand(1); 526 int hasNoData = 0; 527 double noDataValue = band->GetNoDataValue(&hasNoData); 528 529 OE_INFO << "NoData value: " << (hasNoData ? std::to_string(noDataValue) : "Not set") << std::endl; 530 531 // 检查投影 532 const char* projection = ds->GetProjectionRef(); 533 if (projection && strlen(projection) > 0) { 534 OE_INFO << "Projection: [Set]" << std::endl; 535 } else { 536 OE_WARN << "Projection: [Missing]" << std::endl; 537 } 538 539 // 检查地理变换 540 double geoTransform[6]; 541 if (ds->GetGeoTransform(geoTransform) == CE_None) { 542 OE_INFO << "GeoTransform: [" 543 << geoTransform[0] << ", " << geoTransform[1] << ", " << geoTransform[2] << ", " 544 << geoTransform[3] << ", " << geoTransform[4] << ", " << geoTransform[5] << "]" << std::endl; 545 } else { 546 OE_WARN << "GeoTransform: [Missing]" << std::endl; 547 } 548 } 549 550 GDALClose(ds); 551 return true; 552 } 553 }; 554 555 // 使用示例: 556 void exampleUsage(osgEarth::Map* map) { 557 ElevationDataExporter::ExportOptions options; 558 options.targetLod = 14; 559 options.maxFallbackLevels = 8; 560 options.fillGaps = true; 561 options.fillGapsMaxDistance = 2000.0; 562 options.compression = "DEFLATE"; 563 options.outputResolution = 30.0; // 30米/像素 564 565 double minLon = 116.0; 566 double minLat = 39.0; 567 double maxLon = 117.0; 568 double maxLat = 40.0; 569 std::string outputFile = "elevation_export.tif"; 570 //DP_ENGINE_PTR->GetKernelData()->GetCurMap() 571 bool success = ElevationDataExporter::exportElevationToTiff( 572 map, minLon, minLat, maxLon, maxLat, outputFile, options); 573 574 if (success) { 575 OE_INFO << "Elevation data exported successfully to: " << outputFile << std::endl; 576 } else { 577 OE_WARN << "Failed to export elevation data!" << std::endl; 578 } 579 }
主要修复和改进:
-
移除不存在的函数:删除了
getEquatorialResolution函数调用 -
新的分辨率估算方法:实现了
estimateResolutionFromLOD函数,基于地球赤道周长和LOD来估算分辨率 -
支持用户指定分辨率:添加了
outputResolution选项,让用户可以明确指定输出分辨率 -
改进的地理变换:修正了GDAL地理变换参数,使用像素中心参考
-
手动统计计算:改进了统计信息计算,避免GDAL内部计算可能的问题
-
更好的缝隙填充:使用两遍填充策略,先4-邻域快速填充,再扩大范围填充剩余缝隙
-
更详细的验证:增加了对地理变换参数的验证输出
这个版本应该能够正确处理高程数据的导出,避免拼接缝隙,并提供完整的高程描述信息。

浙公网安备 33010602011771号