這個方案提供了一個完整的從osgEarth提取高程數據並導出為TIFF文件的工具類。

我們想要從osgEarth中提取某個範圍、某個層級的高程數據,並保存為TIFF文件。
步驟:

  1. 獲取高程層(ElevationLayer)的數據。
  2. 確定我們要提取的區域(經緯度範圍)和層級(LOD)。
  3. 遍歷該區域內的所有TileKey,然後通過ElevationLayer獲取每個TileKey對應的高程數據(HeightField)。
  4. 將獲取到的高程數據拼接成一個完整的高程網格。
  5. 使用GDAL將高程網格寫入TIFF文件。

注意:osgEarth中的高程數據可能是多層的,我們可能需要從最底層開始合併,或者直接獲取某一層的數據。

但是,在osgEarth中,高程數據通常以瓦片形式存儲,並且可能有多級細節。我們想要獲取某個層級(LOD)的整個範圍的高程數據,這個層級應該是瓦片金字塔中的某一級。

我們可以通過以下步驟實現:

  1. 獲取Map中的ElevationLayer。
  2. 根據給定的範圍(經緯度)和層級,計算出覆蓋該範圍的所有TileKey。
  3. 對於每個TileKey,使用ElevationLayer::createHeightField方法獲取高程數據。
  4. 將每個HeightField轉換為GDAL可以識別的數據(例如,浮點型數組)。
  5. 使用GDAL創建TIFF文件,設置地理參考信息,並將高程數據寫入。

但是,注意:ElevationLayer可能不止一個,我們可能需要合併多個層(例如,有底圖和細節層)。這裏我們假設使用最上面的層(即最詳細的層)或者合併所有層。

在osgEarth中,我們可以通過MapFrame獲取高程層,然後使用ElevationLayerVector的populateHeightFieldAndNormalMap方法來合併多個層。

然而,我們這裏只想要某個層級的數據,所以我們可以直接使用ElevationLayer的createHeightField方法,但是要注意,這個方法可能會從多個層中合併數據(如果設置了多個層的話)。如果我們想要原始數據,可能需要直接訪問底層的TileSource。

但是,為了簡單起見,我們假設使用當前激活的ElevationLayer(可能只有一個)來獲取數據。

另一種方法是使用ElevationQuery,但是ElevationQuery通常用於查詢點的高程,而不是批量提取。

我們也可以使用ElevationPool來獲取數據,但是ElevationPool也是用於查詢的。

因此,我們選擇直接使用ElevationLayer和TileKey來獲取數據。

步驟詳解:

  1. 獲取Map對象,然後獲取ElevationLayer。
  2. 根據給定的層級,生成該層級下覆蓋我們指定範圍的所有TileKey。
  3. 對於每個TileKey,調用ElevationLayer::createHeightField來獲取高程數據。
  4. 將每個HeightField轉換到同一座標系(例如WGS84)並拼接成一個大的高程網格。
  5. 使用GDAL寫入TIFF。

但是,注意:每個TileKey對應的高程數據可能是不同的座標系,我們需要將它們轉換到同一個座標系(例如,地理座標系WGS84)下。

另外,我們還需要注意高程數據的垂直基準面(通常我們使用海平面為0)。

由於我們只是保存為TIFF,我們可以將高程數據轉換為WGS84地理座標系,並保存為GeoTIFF。

下面是一個大致的代碼框架:

注意:以下代碼僅為示例,需要根據實際環境調整。

假設我們有一個osgEarth::Map對象,我們想要提取層級為lod,範圍為[minLon, minLat, maxLon, maxLat]的高程數據。

我們假設高程數據在WGS84座標系下。

步驟:

  1. 獲取ElevationLayer。
  2. 獲取Profile(通常是全球剖分網格,如WGS84)。
  3. 根據範圍和層級,計算TileKey。
  4. 遍歷TileKey,獲取HeightField。
  5. 將HeightField轉換為GDAL可用的數據。
  6. 使用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。

步驟:

  1. 獲取Map的Profile。
  2. 根據範圍和層級,計算TileKey的範圍。
  3. 遍歷TileKey,獲取每個TileKey的高程數據。
  4. 將每個TileKey的高程數據轉換到WGS84,並拼接成一個大的高程網格。

但是,重投影每個TileKey可能會很慢,所以我們也可以先獲取每個TileKey的數據,然後整體重投影。但是,這裏我們為了簡單,假設ElevationLayer的數據已經是WGS84。

實際上,osgEarth的高程層可能使用不同的SRS,我們需要在獲取高程數據時指定SRS,或者將獲取的高程數據轉換到WGS84。

我們可以在創建HeightField時指定SRS,或者使用GeoHeightField類來轉換。

在osgEarth中,GeoHeightField可以處理重投影。

因此,我們使用GeoHeightField來將高程數據轉換到WGS84。

具體步驟:

  1. 獲取Map的ElevationLayer。
  2. 獲取Map的Profile,我們使用這個Profile來生成TileKey。
  3. 計算給定範圍和層級的TileKey列表。
  4. 創建一個大的高程網格(二維數組),大小由範圍和在層級下的分辨率決定。
  5. 對於每個TileKey,使用ElevationLayer創建HeightField,然後轉換為GeoHeightField(使用TileKey的Extent和SRS),然後重投影到WGS84。
  6. 將每個GeoHeightField的數據拷貝到大的高程網格的相應位置。
  7. 使用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)

 

這個解決方案的主要特點:

  1. 範圍提取:根據指定的地理範圍(經緯度)和層級(LOD)提取高程數據
  2. 多tile合併:自動處理覆蓋指定範圍的多個高程瓦片
  3. GDAL輸出:使用GDAL庫將數據保存為標準的GeoTIFF格式
  4. 地理參考:在TIFF文件中包含正確的地理變換和投影信息

注意事項:

  1. 根據實際數據範圍調整tile合併邏輯
  2. 處理大範圍數據時注意內存使用
  3. 可以根據需要調整輸出數據的精度和格式

 

主要改進點:

  1. 回退機制:getHeightFieldWithFallback 函數在找不到請求層級數據時,會自動向父層級回退
  2. 動態尺寸計算:calculateTileDimensions 會根據實際可用的數據源確定tile尺寸
  3. 重採樣支持:當回退到的層級tile尺寸與預期不符時,會自動進行雙線性插值重採樣
  4. 靈活配置:可以通過 maxFallbackLevels 參數控制最大回退層級數
  5. 詳細日誌:添加了詳細的日誌輸出,便於調試和數據獲取情況追蹤

這樣修改後,即使請求的高層級數據不存在,系統也會自動使用較低層級的可用數據,大大提高了數據導出的成功率。

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 }

 

主要修復和改進:

  1. 移除不存在的函數:刪除了 getEquatorialResolution 函數調用
  2. 新的分辨率估算方法:實現了 estimateResolutionFromLOD 函數,基於地球赤道周長和LOD來估算分辨率
  3. 支持用户指定分辨率:添加了 outputResolution 選項,讓用户可以明確指定輸出分辨率
  4. 改進的地理變換:修正了GDAL地理變換參數,使用像素中心參考
  5. 手動統計計算:改進了統計信息計算,避免GDAL內部計算可能的問題
  6. 更好的縫隙填充:使用兩遍填充策略,先4-鄰域快速填充,再擴大範圍填充剩餘縫隙
  7. 更詳細的驗證:增加了對地理變換參數的驗證輸出

這個版本應該能夠正確處理高程數據的導出,避免拼接縫隙,並提供完整的高程描述信息。