WorldWind学习系列十四:DEM数据加载和应用——以SRTM为例

  今天是2010的第一天,总想把它过得充实点,为我自己新的一年开个好头吧!首先,向关注我博客的网友道声:“元旦快乐!”,其次,接着跟大家分享一下我学习WW中DEM数据的加载和应用心得,希望大家从中有所收获!

  DEM应用在WW的三维表现中占有很重要的位置,跟影像数据同等重要!幸好影像和DEM的加载和处理原理上几乎一致,对基于WW搞GIS三维开发来说是件好事,理解好任何一种,另一种触类旁通!前一篇,主要从功能上做了简单入门介绍,该篇将从代码级别分析WW内置的SRTM的DEM数据加载和应用,下一篇讲从二次开发角度上讲解如何处理、配置自己的影像和DEM数据。呵呵,因为DEM部分很重要,且是放假期间我也有时间,争取篇篇精彩!

  两个缩写词介绍:因为这两个缩写词常出现,知道是什么缩写,就不觉得神秘啦!

  SRTM:The Shuttle Radar Topography Mission (SRTM) obtained elevation data on a near-global scale to generate the most complete high-resolution digital topographic database of Earth. SRTM consisted of a specially modified radar system that flew onboard the Space Shuttle Endeavour during an 11-day mission in February of 2000.

  NLT:NASA Learning Technologies.

  我从BMNG.cs为例入手研究DEM的使用,当然研究瓦片影像也该从此入手,但,今天影像不是我们关注的重点。现在正式步入主题,跟我一起分析和学习代码吧!

  BMNG.cs类144行构造函数中代码,

   WorldWind. NltImageStore  imageStore  =   new  WorldWind.NltImageStore(String.Format( " bmng.topo.2004{0:D2} " , i  +   1 ),  " http://worldwind25.arc.nasa.gov/tile/tile.aspx " );
                imageStore.DataDirectory 
=   null ;
                imageStore.LevelZeroTileSizeDegrees 
=   36.0 ;
                imageStore.LevelCount 
=   5 ;
                imageStore.ImageExtension 
=   " jpg " ;
                imageStore.CacheDirectory 
=  String.Format( " {0}\\BMNG\\{1} " , m_WorldWindow.Cache.CacheDirectory, String.Format( " BMNG (Shaded) Tiled - {0}.2004 " , i  +   1 ));

                ias 
=   new  WorldWind.ImageStore[ 1 ];
                ias[
0 =  imageStore;

                m_QuadTileLayers[
0 , i]  =   new  WorldWind.Renderable. QuadTileSet(
                    String.Format(
" Tiled - {0}.2004 " , i  +   1 ),
                    m_WorldWindow.CurrentWorld,
                    
0 ,
                    
90 - 90 - 180 180 ,
                    
true ,
                    ias);

 

BMNG中的NltImageStore.cs、QuadTileSet类。这是我们关注的对象。
QuadTileSet继承自RenderableObject,是要绘制渲染的对象类。
关注它的562行Update()方法、517行Initialize()方法、 701行Render()方法。
Update()方法

ExpandedBlockStart.gif QuadTileSet的Update()方法
public   override   void  Update(DrawArgs drawArgs)
        {
            
if  ( ! isInitialized)
                Initialize(drawArgs);

            
if  (m_effectPath  !=   null   &&  m_effect  ==   null )
            {
                
string  errs  =   string .Empty;
                m_effect 
=  Effect.FromFile(DrawArgs.Device, m_effectPath,  null "" , ShaderFlags.None, m_effectPool,  out  errs);

                
if  (errs  !=   null   &&  errs  !=   string .Empty)
                {
                    Log.Write(Log.Levels.Warning, 
" Could not load effect  "   +  m_effectPath  +   " "   +  errs);
                    Log.Write(Log.Levels.Warning, 
" Effect has been disabled. " );
                    m_effectPath 
=   null ;
                    m_effect 
=   null ;
                }
            }

            
if  (ImageStores[ 0 ].LevelZeroTileSizeDegrees  <   180 )
            {
                
//  Check for layer outside view
                 double  vrd  =  DrawArgs.Camera.ViewRange.Degrees;
                
double  latitudeMax  =  DrawArgs.Camera.Latitude.Degrees  +  vrd;
                
double  latitudeMin  =  DrawArgs.Camera.Latitude.Degrees  -  vrd;
                
double  longitudeMax  =  DrawArgs.Camera.Longitude.Degrees  +  vrd;
                
double  longitudeMin  =  DrawArgs.Camera.Longitude.Degrees  -  vrd;
                
if  (latitudeMax  <  m_south  ||  latitudeMin  >  m_north  ||  longitudeMax  <  m_west  ||  longitudeMin  >  m_east)
                    
return ;
            }

            
if  (DrawArgs.Camera.ViewRange  *   0.5f   >
                    Angle.FromDegrees(TileDrawDistance 
*  ImageStores[ 0 ].LevelZeroTileSizeDegrees))
            {
                
lock  (m_topmostTiles.SyncRoot)
                {
                    
foreach  (QuadTile qt  in  m_topmostTiles.Values)
                        qt.Dispose();
                    m_topmostTiles.Clear();
                    ClearDownloadRequests();
                }

                
return ;
            }
       //知识点,可以看看,如何计算不可见瓦片的算法。
            RemoveInvisibleTiles(DrawArgs.Camera);
下面主要是如何计算和加载瓦片式影像的,是重点,但不是这次的重点。
            
try
            {
        //根据Camera所对的中心经纬度,计算中心点的行列号
                
int  middleRow  =  MathEngine.GetRowFromLatitude(DrawArgs.Camera.Latitude, ImageStores[ 0 ].LevelZeroTileSizeDegrees);
                
int  middleCol  =  MathEngine.GetColFromLongitude(DrawArgs.Camera.Longitude, ImageStores[ 0 ].LevelZeroTileSizeDegrees);
         //根据行列号,反推瓦片的四点对应的经度或纬度
                
double  middleSouth  =   - 90.0f   +  middleRow  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                
double  middleNorth  =   - 90.0f   +  middleRow  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees  +  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                
double  middleWest  =   - 180.0f   +  middleCol  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                
double  middleEast  =   - 180.0f   +  middleCol  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees  +  ImageStores[ 0 ].LevelZeroTileSizeDegrees;

                
double  middleCenterLat  =   0.5f   *  (middleNorth  +  middleSouth);
                
double  middleCenterLon  =   0.5f   *  (middleWest  +  middleEast);
 //这里存在一个算法,由中心瓦片框,向四周扩散地找相邻的瓦片矩形框。
//有兴趣的网友可以看一下,根据算法画出图来就好理解啦。(我感觉该算法对以后开发会有用的)
                
int  tileSpread  =   4 ;
                
for  ( int  i  =   0 ; i  <  tileSpread; i ++ )
                {
                    
for  ( double  j  =  middleCenterLat  -  i  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees; j  <  middleCenterLat  +  i  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees; j  +=  ImageStores[ 0 ].LevelZeroTileSizeDegrees)
                    {
                        
for  ( double  k  =  middleCenterLon  -  i  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees; k  <  middleCenterLon  +  i  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees; k  +=  ImageStores[ 0 ].LevelZeroTileSizeDegrees)
                        {
        //根据经\纬度和tileSize来计算行列号,这里LevelZeroTileSizeDegrees为第0层的瓦片大小为36度,瓦片总个数为50片
                            
int  curRow  =  MathEngine. GetRowFromLatitude (Angle.FromDegrees(j), ImageStores[ 0 ].LevelZeroTileSizeDegrees);
                            
int  curCol  =  MathEngine. GetColFromLongitude (Angle.FromDegrees(k), ImageStores[ 0 ].LevelZeroTileSizeDegrees);
                            
long  key  =  (( long )curRow  <<   32 +  curCol;
                //如果集合m_topmostTiles已经存在QuadTile,则更新QuadTile
                            
QuadTile qt  =  (QuadTile)m_topmostTiles[key];
                            
if  (qt  !=   null )
                            {
                                
qt.Update(drawArgs);
                                
continue ;
                            }

                          
//  Check for tile outside layer boundaries,获取外边框四点经度或纬度坐标
                             double  west  =   - 180.0f   +  curCol  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                            
if  (west  >  m_east)
                                
continue ;

                            
double  east  =  west  +  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                            
if  (east  <  m_west)
                                
continue ;

                            
double  south  =   - 90.0f   +  curRow  *  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                            
if  (south  >  m_north)
                                
continue ;

                            
double  north  =  south  +  ImageStores[ 0 ].LevelZeroTileSizeDegrees;
                            
if  (north  <  m_south)
                                
continue ;
                //结合中不存在,创建新的QuadTile
                            
qt  =   new  QuadTile(south, north, west, east,  0 this );
                 //判断新的QuadTile是否在可视区域中。(可以关注一下:Intersects()方法判断矩形框相交)
                if  (DrawArgs.Camera.ViewFrustum.Intersects(qt.BoundingBox))
                            {
                                
lock  (m_topmostTiles.SyncRoot)
                                    m_topmostTiles.Add(key, qt);
                  //调用QuadTile的Update()方法
                                 qt.Update(drawArgs);
                            }
                        }
                    }
                }
            }
            
catch  (System.Threading.ThreadAbortException)
            {
            }
            
catch  (Exception caught)
            {
                Log.Write(caught);
            }
        }

 

 Render()方法的关键代码为:

                    device.VertexFormat = CustomVertex.PositionNormalTextured.Format;
                    foreach (QuadTile qt in m_topmostTiles.Values)
                        qt.Render(drawArgs);

从上面可以看出,QuadTileSet可看作是QuadTile的集合,真正实现更新和渲染的是QuadTile对象。里面有影像的加载和渲染绘制,也有DEM的渲染绘制。

  我们先看看QuadTile.cs 中Update()方法:

ExpandedBlockStart.gif QuadTile的Update()代码
     public   virtual   void  Update(DrawArgs drawArgs)
        {
            
            
if  (m_isResetingCache)
                
return ;

            
try
            {
                
double  tileSize  =  North  -  South;

                
if  ( ! isInitialized)
                {
                    
if  (DrawArgs.Camera.ViewRange  *   0.5f   <  Angle.FromDegrees(QuadTileSet.TileDrawDistance  *  tileSize)
    
&&  MathEngine.SphericalDistance(CenterLatitude, CenterLongitude,
                            DrawArgs.Camera.Latitude, DrawArgs.Camera.Longitude) 
<  Angle.FromDegrees(QuadTileSet.TileDrawSpread  *  tileSize  *   1.25f )
    
&&  DrawArgs.Camera.ViewFrustum.Intersects(BoundingBox)
                        )
                        Initialize();
                }

                
if  (isInitialized  &&  World.Settings.VerticalExaggeration  !=  verticalExaggeration  ||  m_CurrentOpacity  !=  QuadTileSet.Opacity  ||
                    QuadTileSet.RenderStruts 
!=  renderStruts)
                {
           //创建瓦片网格(重点)
                   
 CreateTileMesh();
                }
                
if  (isInitialized)
                {
          //判断进入下一层的条件(ViewRange角度、球面距离、可视区域)
                    
if  (DrawArgs.Camera.ViewRange  <  Angle.FromDegrees(QuadTileSet.TileDrawDistance  *  tileSize)
    
&&  MathEngine.SphericalDistance(CenterLatitude, CenterLongitude,
                            DrawArgs.Camera.Latitude, DrawArgs.Camera.Longitude) 
<  Angle.FromDegrees(QuadTileSet.TileDrawSpread  *  tileSize)
    
&&  DrawArgs.Camera.ViewFrustum.Intersects(BoundingBox)
                        )
                    {
                        
if  (northEastChild  ==   null   ||  northWestChild  ==   null   ||  southEastChild  ==   null   ||  southWestChild  ==   null )
                        {  
                //计算下一级别的 四个子瓦片 (重点,稍后一起看看)
                            
ComputeChildren(drawArgs);
                        }

                        
if  (northEastChild  !=   null )
                        {
                            northEastChild.Update(drawArgs);
                        }

                        
if  (northWestChild  !=   null )
                        {
                            northWestChild.Update(drawArgs);
                        }

                        
if  (southEastChild  !=   null )
                        {
                            southEastChild.Update(drawArgs);
                        }

                        
if  (southWestChild  !=   null )
                        {
                            southWestChild.Update(drawArgs);
                        }
                    }
                    
else
                    {
                        
if  (northWestChild  !=   null )
                        {
                            northWestChild.Dispose();
                            northWestChild 
=   null ;
                        }

                        
if  (northEastChild  !=   null )
                        {
                            northEastChild.Dispose();
                            northEastChild 
=   null ;
                        }

                        
if  (southEastChild  !=   null )
                        {
                            southEastChild.Dispose();
                            southEastChild 
=   null ;
                        }

                        
if  (southWestChild  !=   null )
                        {
                            southWestChild.Dispose();
                            southWestChild 
=   null ;
                        }
                    }
                }

                
if  (isInitialized)
                {
                    
if  (DrawArgs.Camera.ViewRange  /   2   >  Angle.FromDegrees(QuadTileSet.TileDrawDistance  *  tileSize  *   1.5f )
                            
||  MathEngine.SphericalDistance(CenterLatitude, CenterLongitude, DrawArgs.Camera.Latitude, DrawArgs.Camera.Longitude)  >  Angle.FromDegrees(QuadTileSet.TileDrawSpread  *  tileSize  *   1.5f ))
                    {
                        
if  (Level  !=   0   ||  (Level  ==   0   &&   ! QuadTileSet.AlwaysRenderBaseTiles))
                            
this .Dispose();
                    }
                }
            }
            
catch
            {
            }
        }

 

创建下一级的四个瓦片的方法:(可以被我们重用)

ExpandedBlockStart.gif ComputeChildren(DrawArgs drawArgs)
         public   virtual   void  ComputeChildren(DrawArgs drawArgs)
        {
       //判断是否是最高级别
            
if  (Level  +   1   >=  QuadTileSet.ImageStores[ 0 ].LevelCount)
                
return ;
       //计算瓦片的中点经纬度
            
double  CenterLat  =   0.5f   *  (South  +  North);
            
double  CenterLon  =   0.5f   *  (East  +  West);
            
if  (northWestChild  ==   null )
                northWestChild 
=  ComputeChild(CenterLat, North, West, CenterLon);

            
if  (northEastChild  ==   null )
                northEastChild 
=  ComputeChild(CenterLat, North, CenterLon, East);

            
if  (southWestChild  ==   null )
                southWestChild 
=  ComputeChild(South, CenterLat, West, CenterLon);

            
if  (southEastChild  ==   null )
                southEastChild 
=  ComputeChild(South, CenterLat, CenterLon, East);
        }

 

 

ExpandedBlockStart.gif ComputeChild(double childSouth, double childNorth, double childWest, double childEast)
         ///   <summary>
        
///  Returns the QuadTile for specified location if available.
        
///  Tries to queue a download if not available.
        
///   </summary>
        
///   <returns> Initialized QuadTile if available locally, else null. </returns>
         private  QuadTile ComputeChild( double  childSouth,  double  childNorth,  double  childWest,  double  childEast)
        {
            QuadTile child 
=   new  QuadTile(
                childSouth,
                childNorth,
                childWest,
                childEast,
                
this .Level  +   1 ,
                QuadTileSet);

            
return  child;
        }

 

QuadTile.cs 中的CreateTileMesh()方法用来创建瓦片格网的,分别在Initialize() 、Update()方法中调用。

410行 这里调用的CreateElevatedMesh();是添加DEM数据创建高程格网的。

         ///   <summary>
        
///  Builds flat or terrain mesh for current tile
        
///   </summary>
         public   virtual   void  CreateTileMesh()
        {
            verticalExaggeration 
=  World.Settings.VerticalExaggeration;
            m_CurrentOpacity 
=  QuadTileSet.Opacity;
            renderStruts 
=  QuadTileSet.RenderStruts;

            
if  (QuadTileSet.TerrainMapped  &&  Math.Abs(verticalExaggeration)  >  1e - 3 )
         //创建具有高程值的格网(今天要关注的)
                
CreateElevatedMesh();
            
else
                //创建没有高程值的平面格网         
                CreateFlatMesh();
        }

591行CreateElevatedMesh()

ExpandedBlockStart.gif 创建具有高程值的格网
         ///   <summary>
        
///  Build the elevated terrain mesh
        
///   </summary>
         protected   virtual   void  CreateElevatedMesh()
        {
            isDownloadingTerrain 
=   true ;
            //vertexCountElevated值为40;向四周分别扩充一个样本点
            
//  Get height data with one extra sample around the tile
             double  degreePerSample  =  LatitudeSpan  /  vertexCountElevated;
            //获取具有高程值的TerrainTile对象(这是最关键部分,深入分析)
            TerrainTile tile 
=   QuadTileSet.World.TerrainAccessor. GetElevationArray (North  +  degreePerSample, South  -  degreePerSample, West  -  degreePerSample, East  +  degreePerSample, vertexCountElevated  +   3 );
            
float [,] heightData  =  tile.ElevationData;

            
int  vertexCountElevatedPlus3  =  vertexCountElevated  /   2   +   3 ;
            
int  totalVertexCount  =  vertexCountElevatedPlus3  *  vertexCountElevatedPlus3;
            northWestVertices 
=   new  CustomVertex.PositionNormalTextured[totalVertexCount];
            southWestVertices 
=   new  CustomVertex.PositionNormalTextured[totalVertexCount];
            northEastVertices 
=   new  CustomVertex.PositionNormalTextured[totalVertexCount];
            southEastVertices 
=   new  CustomVertex.PositionNormalTextured[totalVertexCount];
            
double  layerRadius  =  ( double )QuadTileSet.LayerRadius;

            
//  Calculate mesh base radius (bottom vertices)
            
//  Find minimum elevation to account for possible bathymetry
             float  minimumElevation  =   float .MaxValue;
            
float  maximumElevation  =   float .MinValue;
            
foreach  ( float  height  in  heightData)
            {
                
if  (height  <  minimumElevation)
                    minimumElevation 
=  height;
                
if  (height  >  maximumElevation)
                    maximumElevation 
=  height;
            }
            minimumElevation 
*=  verticalExaggeration;
            maximumElevation 
*=  verticalExaggeration;

            
if  (minimumElevation  >  maximumElevation)
            {
                
//  Compensate for negative vertical exaggeration
                minimumElevation  =  maximumElevation;
                maximumElevation 
=  minimumElevation;
            }

            
double  overlap  =   500   *  verticalExaggeration;  //  500m high tiles

            
//  Radius of mesh bottom grid
            meshBaseRadius  =  layerRadius  +  minimumElevation  -  overlap;

            CreateElevatedMesh(ChildLocation.NorthWest, northWestVertices, meshBaseRadius, heightData);
            CreateElevatedMesh(ChildLocation.SouthWest, southWestVertices, meshBaseRadius, heightData);
            CreateElevatedMesh(ChildLocation.NorthEast, northEastVertices, meshBaseRadius, heightData);
            CreateElevatedMesh(ChildLocation.SouthEast, southEastVertices, meshBaseRadius, heightData);

            BoundingBox 
=   new  BoundingBox(( float )South, ( float )North, ( float )West, ( float )East,
                (
float )layerRadius, ( float )layerRadius  +   10000   *   this .verticalExaggeration);

            QuadTileSet.IsDownloadingElevation 
=   false ;

            
//  Build common set of indexes for the 4 child meshes    
             int  vertexCountElevatedPlus2  =  vertexCountElevated  /   2   +   2 ;
            vertexIndexes 
=   new   short [ 2   *  vertexCountElevatedPlus2  *  vertexCountElevatedPlus2  *   3 ];

            
int  elevated_idx  =   0 ;
            
for  ( int  i  =   0 ; i  <  vertexCountElevatedPlus2; i ++ )
            {
                
for  ( int  j  =   0 ; j  <  vertexCountElevatedPlus2; j ++ )
                {
                    vertexIndexes[elevated_idx
++ =  ( short )(i  *  vertexCountElevatedPlus3  +  j);
                    vertexIndexes[elevated_idx
++ =  ( short )((i  +   1 *  vertexCountElevatedPlus3  +  j);
                    vertexIndexes[elevated_idx
++ =  ( short )(i  *  vertexCountElevatedPlus3  +  j  +   1 );

                    vertexIndexes[elevated_idx
++ =  ( short )(i  *  vertexCountElevatedPlus3  +  j  +   1 );
                    vertexIndexes[elevated_idx
++ =  ( short )((i  +   1 *  vertexCountElevatedPlus3  +  j);
                    vertexIndexes[elevated_idx
++ =  ( short )((i  +   1 *  vertexCountElevatedPlus3  +  j  +   1 );
                }
            }

            calculate_normals(
ref  northWestVertices, vertexIndexes);
            calculate_normals(
ref  southWestVertices, vertexIndexes);
            calculate_normals(
ref  northEastVertices, vertexIndexes);
            calculate_normals(
ref  southEastVertices, vertexIndexes);

            isDownloadingTerrain 
=   false ;
        }

 596行TerrainTile tile = QuadTileSet.World.TerrainAccessor.GetElevationArray(North + degreePerSample, South - degreePerSample, West - degreePerSample, East + degreePerSample, vertexCountElevated + 3);
获取样本点的高程值数组。

使用了TerrainAccessor.cs类120行代码

   public   virtual  TerrainTile GetElevationArray( double  north,  double  south,  double  west,  double  east,  int  samples)
  {
   TerrainTile res 
=   null ;
   res 
=   new  TerrainTile( null );
   res.North 
=  north;
   res.South 
=  south;
   res.West 
=  west;
   res.East 
=  east;
   res.SamplesPerTile 
=  samples;
   res.IsInitialized 
=   true ;
   res.IsValid 
=   true ;

   
double  latrange  =  Math.Abs(north  -  south);
   
double  lonrange  =  Math.Abs(east  -  west);

   
float [,] data  =   new   float [samples,samples];
   
float  scaleFactor  =  ( float ) 1 / (samples  -   1 );
   
for ( int  x  =   0 ; x  <  samples; x ++ )
   {
    
for ( int  y  =   0 ; y  <  samples; y ++ )
    {
     
double  curLat  =  north  -  scaleFactor  *  latrange  *  x;
     
double  curLon  =  west  +  scaleFactor  *  lonrange  *  y;
    //关键,获取瓦片所有样本点的高程值
    
 data[x,y]  =  GetElevationAt(curLat, curLon,  0 );
    }
   }
   res.ElevationData 
=  data;
   
return  res;
  }

 

 关键代码:data[x,y] = GetElevationAt(curLat, curLon, 0);

GetElevationAt();在TerrainAccessor.cs是抽象方法。真正实现是在TerrainAccessor的子类NltTerrainAccessor中重载实现的。
120行  public override float GetElevationAt(double latitude, double longitude)
  {
   return GetElevationAt(latitude, longitude, m_terrainTileService.SamplesPerTile / m_terrainTileService.LevelZeroTileSizeDegrees);
  }

 

TerrainAccessor对象哪里来的(即:在哪完成初始化和传入的?)

 ConfigurationLoader.cs的Load()方法的97行代码:

TerrainAccessor[] terrainAccessor = getTerrainAccessorsFromXPathNodeIterator(worldIter.Current.Select("TerrainAccessor"),
                        System.IO.Path.Combine(cache.CacheDirectory, worldName));

                    World newWorld = new World(
                        worldName,
                        new Microsoft.DirectX.Vector3(0, 0, 0),
                        new Microsoft.DirectX.Quaternion(0, 0, 0, 0),
                        equatorialRadius,
                        cache.CacheDirectory,
                        (terrainAccessor != null ? terrainAccessor[0] : null)//TODO: Oops, World should be able to handle an array of terrainAccessors
                        );

然后通过World对象传入到QuadTileSet类中的。

 我们看看getTerrainAccessorsFromXPathNodeIterator方法如何完成完成TerrainAccessor对象。
注意:该方法返回值为TerrainAccessor[],是个数组,为什么呢??(请关注我下一篇文章)

2078行

ExpandedBlockStart.gif getTerrainAccessorsFromXPathNodeIterator(XPathNodeIterator iter, string cacheDirectory)代码
private   static  TerrainAccessor[] getTerrainAccessorsFromXPathNodeIterator(XPathNodeIterator iter,  string  cacheDirectory)
        {
        System.Collections.ArrayList terrainAccessorList 
=   new  System.Collections.ArrayList();
        //下面是读取DEM配置XML,并根据配置信息创建TerrainTileService对象和TerrainAccessor对象
             while (iter.MoveNext())
            {
                
string  terrainAccessorName  =  iter.Current.GetAttribute( " Name " "" );
                
if (terrainAccessorName  ==   null )
                {
                    
//  TODO: Throw exception?
                     continue ;
                }
                XPathNodeIterator latLonBoxIter 
=  iter.Current.Select( " LatLonBoundingBox " );
                
if (latLonBoxIter.Count  !=   1 )
                {
                    
//  TODO: Throw exception?
                     continue ;
                }
                
double  north  =   0 ;
                
double  south  =   0 ;
                
double  west  =   0 ;
                
double  east  =   0 ;

                latLonBoxIter.MoveNext();

                north 
=  ParseDouble(getInnerTextFromFirstChild(latLonBoxIter.Current.Select( " North " )));
                south 
=  ParseDouble(getInnerTextFromFirstChild(latLonBoxIter.Current.Select( " South " )));
                west 
=  ParseDouble(getInnerTextFromFirstChild(latLonBoxIter.Current.Select( " West " )));
                east 
=  ParseDouble(getInnerTextFromFirstChild(latLonBoxIter.Current.Select( " East " )));
                

                TerrainAccessor[] higerResolutionSubsets 
=  getTerrainAccessorsFromXPathNodeIterator(
                    iter.Current.Select(
" HigherResolutionSubsets " ),
                    Path.Combine(cacheDirectory, terrainAccessorName));

                XPathNodeIterator tileServiceIter 
=  iter.Current.Select( " TerrainTileService " );
                
if (tileServiceIter.Count  ==   1 )
                {
                    
string  serverUrl  =   null ;
                    
string  dataSetName  =   null ;
                    
double  levelZeroTileSizeDegrees  =   double .NaN;
                    
uint  numberLevels  =   0 ;
                    
uint  samplesPerTile  =   0 ;
                    
string  dataFormat  =   null ;
                    
string  fileExtension  =   null ;
                    
string  compressionType  =   null ;

                    tileServiceIter.MoveNext();
                    
                    serverUrl 
=  getInnerTextFromFirstChild(tileServiceIter.Current.Select( " ServerUrl " ));
                    dataSetName 
=  getInnerTextFromFirstChild(tileServiceIter.Current.Select( " DataSetName " ));
                    levelZeroTileSizeDegrees 
=  ParseDouble(getInnerTextFromFirstChild(tileServiceIter.Current.Select( " LevelZeroTileSizeDegrees " )));
                    numberLevels 
=   uint .Parse(getInnerTextFromFirstChild(tileServiceIter.Current.Select( " NumberLevels " )));
                    samplesPerTile 
=   uint .Parse(getInnerTextFromFirstChild(tileServiceIter.Current.Select( " SamplesPerTile " )));
                    dataFormat 
=  getInnerTextFromFirstChild(tileServiceIter.Current.Select( " DataFormat " ));
                    fileExtension 
=  getInnerTextFromFirstChild(tileServiceIter.Current.Select( " FileExtension " ));
                    compressionType 
=  getInnerTextFromFirstChild(tileServiceIter.Current.Select( " CompressonType " ));
            
//根据配置信息创建TerrainTileService对象和TerrainAccessor对象
                    TerrainTileService tts 
=   new  TerrainTileService(
                        serverUrl,
                        dataSetName,
                        levelZeroTileSizeDegrees,
                        (
int )samplesPerTile,
                        fileExtension,
                        (
int )numberLevels,
                        Path.Combine(cacheDirectory, terrainAccessorName),
                        World.Settings.TerrainTileRetryInterval,
                        dataFormat);

                    TerrainAccessor newTerrainAccessor 
=   new  NltTerrainAccessor(
                        terrainAccessorName,
                        west,
                        south,
                        east,
                        north,
                        tts,
                        higerResolutionSubsets);

                    terrainAccessorList.Add(newTerrainAccessor);
                }
                
// TODO: Add Floating point terrain Accessor code
                
// TODO: Add WMSAccessor code and make it work in TerrainAccessor (which it currently doesn't)
            }

            
if (terrainAccessorList.Count  >   0 )
            {
                
return  (TerrainAccessor[])terrainAccessorList.ToArray( typeof (TerrainAccessor));
            }
            
else
            {
                
return   null ;
            }
        }

 

再来看看DEM的配置在哪里和XML内容吧
路径:
C:\Program Files\NASA\World Wind 1.4\Config\Earth.xml
配置内容:

?xml version="1.0" encoding="UTF-8"?>
< World  Name ="Earth"  EquatorialRadius ="6378137.0"  LayerDirectory ="Earth"  xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation ="WorldXmlDescriptor.xsd" >
    
< TerrainAccessor  Name ="SRTM" >    //黄色之间的XML就是一个TerrainAccessor配置
        
< TerrainTileService >
            
< ServerUrl > http://worldwind25.arc.nasa.gov/wwelevation/wwelevation.aspx </ ServerUrl >
            
< DataSetName > srtm30pluszip </ DataSetName >
            
< LevelZeroTileSizeDegrees > 20.0 </ LevelZeroTileSizeDegrees >
            
< NumberLevels > 12 </ NumberLevels >
            
< SamplesPerTile > 150 </ SamplesPerTile >
            
< DataFormat > Int16 </ DataFormat >
            
< FileExtension > bil </ FileExtension >
            
< CompressonType > zip </ CompressonType >
        
</ TerrainTileService >
        
< LatLonBoundingBox >
            
< North >
                
< Value > 90.0 </ Value >
            
</ North >
            
< South >
                
< Value > -90.0 </ Value >
            
</ South >
            
< West >
                
< Value > -180.0 </ Value >
            
</ West >
            
< East >
                
< Value > 180.0 </ Value >
            
</ East >
        
</ LatLonBoundingBox >
    
</ TerrainAccessor >
</ World >

 

 


接着上面的讲NltTerrainAccessor类76行代码GetElevationAt(double latitude, double longitude, double targetSamplesPerDegree)方法。

ExpandedBlockStart.gif 获取特定点的高程值
     ///   <summary>
        
///  Get terrain elevation at specified location.  
        
///   </summary>
        
///   <param name="latitude"> Latitude in decimal degrees. </param>
        
///   <param name="longitude"> Longitude in decimal degrees. </param>
        
///   <param name="targetSamplesPerDegree"></param>
        
///   <returns> Returns 0 if the tile is not available on disk. </returns>
         public   override   float  GetElevationAt( double  latitude,  double  longitude,  double  targetSamplesPerDegree)
        {
            
try
            {
                
if  (m_terrainTileService  ==   null   ||  targetSamplesPerDegree  <  World.Settings.MinSamplesPerDegree)
                    
return   0 ;

                
if  (m_higherResolutionSubsets  !=   null )
                {
                    
foreach  (TerrainAccessor higherResSub  in  m_higherResolutionSubsets)
                    {
                        
if  (latitude  >  higherResSub.South  &&  latitude  <  higherResSub.North  &&
                            longitude 
>  higherResSub.West  &&  longitude  <  higherResSub.East)
                        {
                            
return  higherResSub.GetElevationAt(latitude, longitude, targetSamplesPerDegree);
                        }
                    }
                }
               //自己可以看看如何完成TerrainTile的初始化构建的
                TerrainTile tt 
=  m_terrainTileService. GetTerrainTile(latitude, longitude, targetSamplesPerDegree);
                TerrainTileCacheEntry ttce 
=  (TerrainTileCacheEntry)m_tileCache[tt.TerrainTileFilePath];
                
if  (ttce  ==   null )
                {
                    ttce 
=   new  TerrainTileCacheEntry(tt);
                    AddToCache(ttce);
                }

                
if  ( ! ttce.TerrainTile.IsInitialized)
                    ttce.TerrainTile.Initialize();
                ttce.LastAccess 
=  DateTime.Now;
               //获取高程值
                 return   ttce.TerrainTile.GetElevationAt(latitude, longitude);
            }
            
catch  (Exception)
            {
            }
            
return   0 ;
        }

 

 上面获取高程值的关键:TerrainTile类的330行的GetElevationAt(double latitude, double longitude)方法

     public   float  GetElevationAt( double  latitude,  double  longitude)
        {
            
try
            {
                
double  deltaLat  =  North  -  latitude;
                
double  deltaLon  =  longitude  -  West;
//TileSizeDegrees为当前级别下瓦片的度数大小
//计算方法:158行tile.TileSizeDegrees = m_levelZeroTileSizeDegrees * Math.Pow(0.5, tile.TargetLevel);
//注意思考:SamplesPerTile-1为什么是减去1?传进来的SamplesPerTile=43而不是44?
//如果传入的是44,这里应该减2
                
double  df2  =  (SamplesPerTile - 1 /   TileSizeDegrees;
                
float  lat_pixel  =  ( float )(deltaLat  *  df2);
                
float  lon_pixel  =  ( float )(deltaLon  *  df2);
                //这里是将点,近似成包含点的最小正方形(经纬度取整)
                
int  lat_min  =  ( int )lat_pixel;
                
int  lat_max  =  ( int )Math.Ceiling(lat_pixel);
                
int  lon_min  =  ( int )lon_pixel;
                
int  lon_max  =  ( int )Math.Ceiling(lon_pixel);

                
if (lat_min  >=  SamplesPerTile)
                    lat_min 
=  SamplesPerTile  -   1 ;
                
if (lat_max  >=  SamplesPerTile)
                    lat_max 
=  SamplesPerTile  -   1 ;
                
if (lon_min  >=  SamplesPerTile)
                    lon_min 
=  SamplesPerTile  -   1 ;
                
if (lon_max  >=  SamplesPerTile)
                    lon_max 
=  SamplesPerTile  -   1 ;

                
if (lat_min  <   0 )
                    lat_min 
=   0 ;
                
if (lat_max  <   0 )
                    lat_max 
=   0 ;
                
if (lon_min  <   0 )
                    lon_min 
=   0 ;
                
if (lon_max  <   0 )
                    lon_max 
=   0 ;

                
float  delta  =  lat_pixel  -  lat_min;
                //根据外矩形四顶点的经纬度分别插值计算中间线的高程
                
float  westElevation  =  
                    
ElevationData [lon_min, lat_min] * ( 1 - delta)  +  
                    
ElevationData [lon_min, lat_max] * delta;
            
                
float  eastElevation  =  
                    ElevationData[lon_max, lat_min]
* ( 1 - delta)  +  
                    ElevationData[lon_max, lat_max]
* delta;
            //利用插值计算点的高程值
                delta 
=  lon_pixel  -  lon_min;
                
float  interpolatedElevation  =  
                    westElevation * ( 1 - delta)  +  
                    eastElevation * delta;

                
return  interpolatedElevation;
            }
            
catch
            {
            }
            
return   0 ;
        }

 

public float[,] ElevationData就是存放当前瓦片所有样本点高程值的数值。这是通过Initialize()中读取DEM(.bil)文件来获取的。

ExpandedBlockStart.gif 读取BIL文件高程数据
         ///   <summary>
        
///  This method initializes the terrain tile add switches to
        
///  Initialize floating point/int 16 tiles
        
///   </summary>
         public   void  Initialize()
        {
            
if (IsInitialized)
                
return ;

            
if ( ! File.Exists(TerrainTileFilePath))
            {
                
//  Download elevation
                 if (request == null )
                {
                    
using ( request  =   new  TerrainDownloadRequest( this , m_owner, Row, Col, TargetLevel) )
                    {
                        request.SaveFilePath 
=  TerrainTileFilePath;
                        request.DownloadInForeground();
                    }
                }
            }

            
if (ElevationData == null )
                ElevationData 
=   new   float [SamplesPerTile, SamplesPerTile];

            
if (File.Exists(TerrainTileFilePath))
            {
                
//  Load elevation file
                 try
                {
                    
//  TerrainDownloadRequest's FlagBadTile() creates empty files
                    
//  as a way to flag "bad" terrain tiles.
                    
//  Remove the empty 'flag' files after preset time.
                     try
                    {
                        FileInfo tileInfo 
=   new  FileInfo(TerrainTileFilePath);
                        
if (tileInfo.Length  ==   0 )
                        {
                            TimeSpan age 
=  DateTime.Now.Subtract( tileInfo.LastWriteTime );
                            
if (age  <  m_owner.TerrainTileRetryInterval)
                            {
                                
//  This tile is still flagged bad
                                IsInitialized  =   true ;
                            }
                            
else
                            {
                                
//  remove the empty 'flag' file
                                File.Delete(TerrainTileFilePath);
                            }
                            
return ;
                        }
                    }
                    
catch
                    {
                        
//  Ignore any errors in the above block, and continue.
                        
//  For example, if someone had the empty 'flag' file
                        
//  open, the delete would fail.
                    }
    //读取BIL文件数据的关键代码,可以被我们借鉴使用
                     using ( Stream s  =  File.OpenRead(TerrainTileFilePath))
                    {
                        BinaryReader reader  =   new  BinaryReader(s);
                        
if (m_owner.DataType == " Int16 " )
                        {

                            
for ( int  y  =   0 ; y  <  SamplesPerTile; y ++ )
                                
for ( int  x  =   0 ; x  <  SamplesPerTile; x ++ )
                                    ElevationData[x,y]  =  reader.ReadInt16();
                        }
                        
if (m_owner.DataType == " Float32 " )
                        {
                            
for ( int  y  =   0 ; y  <  SamplesPerTile; y ++ )
                                
for ( int  x  =   0 ; x  <  SamplesPerTile; x ++ )
                                {
                                    ElevationData[x,y]  =  reader.ReadSingle();
                                }
                        }
                        IsInitialized 
=   true ;
                        IsValid 
=   true ;
                    }
                    
return ;
                }
                
catch (IOException)
                {
                    
//  If there is an IO exception when reading the terrain tile,
                    
//  then either something is wrong with the file, or with
                    
//  access to the file, so try and remove it.
                     try
                    {
                        File.Delete(TerrainTileFilePath);
                    }
                    
catch (Exception ex)
                    {
                        
throw   new  ApplicationException(String.Format( " Error while trying to delete corrupt terrain tile {0} " , TerrainTileFilePath), ex);
                    }
                }
                
catch (Exception ex)
                {
                    
//  Some other type of error when reading the terrain tile.
                     throw   new  ApplicationException(String.Format( " Error while trying to read terrain tile {0} " , TerrainTileFilePath), ex);
                }
            }
        }

 

另注:SRTM的高程数据存放路径如下图:(DEM跟影像也是分级存放的,存放方式一致)


 至此,如何加载DEM数据创建网格的过程已经分析完了。

接下来,继续分析QuadTile.cs中CreateElevatedMesh()和Render方法,内容主要是DirectX编程,稍后添加……

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值