现在的位置: 首页 > 综合 > 正文

WorldWind学习系列十一:Virtual Earth插件学习

2012年12月06日 ⁄ 综合 ⁄ 共 14535字 ⁄ 字号 评论关闭

    学习WorldWind有很长时间了,理论学习算是基本完成了。我体会是WW的学习主要分为两大步:WW框架体系学习和WW插件学习。学习WW插件逐步深入后,必然要首先学习Direct3D编程,这也算是我的经验之谈吧。今天Virtual Earth插件学习完成,也标志着我可以从WW理论转向WW实践啦。虽然我总结介绍的是Virtual Earth插件,但是希望网友阅读下面的内容前,最好能够先深入学习Direct3D编程、BMNG和Globe Icon插件的底层渲染,这些都是学习Virtual Earth的基础。

  Virtual Earth插件包括以下几个类:

  VirtualEarthForm 窗体类

  VirtualEarthPlugin插件类,继承自Plugin(重要)

  VeReprojectTilesLayer 渲染对象类,继承自Renderable Object(重要)

  VeTile 瓦片对象类(真正实现大部分功能的)(重要)

  Projection投影变换类(重要)

  Search类

  PushPin类

  我们先看看VirtualEarthPlugin,所有的插件类必须继承自Plugin.cs,必须重写Load()和Unload()方法。这两个方法分别实现插件的加载和卸载。

  Load()方法一般是实现添加菜单和添加工具按钮,跟前面介绍插件都很类似的,自己开发插件时模仿着这套路写就行。

  public override void Load()
        {
            
try
            {
          //判断当前World为Earth(注:其他星体可通过名字判断)
                
if (ParentApplication.WorldWindow.CurrentWorld.IsEarth)
                { 
//初始化VE插件控制窗体
                    m_Form 
= new VirtualEarthForm(ParentApplication);
                    m_Form.Owner 
= ParentApplication;
           
//添加VE插件菜单
                    m_MenuItem 
= new MenuItem("MicroSoft VirtualEarth");
                    m_MenuItem.Click 
+= new EventHandler(menuItemClicked);
                    ParentApplication.PluginsMenu.MenuItems.Add(m_MenuItem);

                    //#if DEBUG
                    string imgPath = Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + "\\Plugins\\VirtualEarth\\VirtualEarthPlugin.png";
                    
//#else
                    
//                    _pluginDir = this.PluginDirectory;
                    
//                    string imgPath = this.PluginDirectory + @"\VirtualEarthPlugin.png";
                    
//#endif
                    if (File.Exists(imgPath) == false)
                    {
                        Utility.Log.Write(
new Exception("imgPath not found " + imgPath));
                    }

            //添加工具按钮,会出现工具栏里
                    m_ToolbarItem 
= new WorldWind.WindowsControlMenuButton(
                        
"MicroSoft VirtualEarth",
                        imgPath,
                        m_Form);

                    ParentApplication.WorldWindow.MenuBar.AddToolsMenuButton(m_ToolbarItem);

                    base.Load();
                }
            }
            
catch (Exception ex)
            {
                Utility.Log.Write(ex);
                
throw;
            }
        }

  Unload()方法就是释放插件窗体,并移除插件菜单项和插件工具按钮。

  我们再来看看VeReprojectTilesLayer 类,像其他插件类一样,要重载Initialize()、Update()、Render()方法,但是,VE插件重点在Upadate()方法,当然他还有其他自己特色的方法。

  Initialize()方法主要是一些该类全局对象的实例化,如:投影、VE控制窗体及VeTile初始化等。

  Render()方法主要是实现插件的三维渲染功能的,但是VE的Render真正实现是调用VeTile类中的Render()方法。

VE插件渲染代码

 /// <summary>
        
/// Draws the layer
        
/// </summary>
        public override void Render(DrawArgs drawArgs)
        {
            
try
            {
                
if (this.isOn == false)
                {
                    
return;
                }

                if (this.isInitialized == false)
                {
                    
return;
                }

                if (drawArgs.device == null)
                    
return;

                if (veTiles != null && veTiles.Count > 0)
                {
                    
//render mesh and tile(s)
                    bool disableZBuffer = false//TODO where do i get this setting
                    
//foreach(VeTile veTile in veTiles)
                    
//{
                    
//    veTile.Render(drawArgs, disableZBuffer);
                    
//}

                    // camera jitter fix
                    drawArgs.device.Transform.World = Matrix.Translation(
                           (
float)-drawArgs.WorldCamera.ReferenceCenter.X,
                        (
float)-drawArgs.WorldCamera.ReferenceCenter.Y,
                        (
float)-drawArgs.WorldCamera.ReferenceCenter.Z
                    );

                    // Clear ZBuffer between layers (as in WW)
                    drawArgs.device.Clear(ClearFlags.ZBuffer, 01.0f0);

                    // Render tiles(这里的GetZoomLevelByTrueViewRange方法是个知识点,该方法是根据WorldCamera视角范围,求取当前球体的缩放层次)
            int zoomLevel = GetZoomLevelByTrueViewRange(drawArgs.WorldCamera.TrueViewRange.Degrees);

        
         //真正的渲染处理是由VeTile类的Render()方法实现的
          
int tileDrawn = VeTile.Render(drawArgs, disableZBuffer, veTiles, zoomLevel); // Try current level first
         if(tileDrawn == 0) VeTile.Render(drawArgs, disableZBuffer, veTiles, prevLvl); // If nothing drawn, render previous level

                    //camera jitter fix
                    drawArgs.device.Transform.World = drawArgs.WorldCamera.WorldMatrix;

                    //Render logo
                    RenderDownloadProgress(drawArgs, null0);

                }

                //else pushpins only
                
//render PushPins
                if (pushPins != null && pushPins.Count > 0)
                {

                    RenderPushPins(drawArgs);

                }
            }
            catch (Exception ex)
            {
                Utility.Log.Write(ex);
            }
        }

 

   VE中获取缩放级别的方法是很巧妙的,根据原作者文章知:VE的缩放级别是1-19级甚至更低,是以2的阶乘递减的。方法我们自己开发时可以重用,但是它的思想还是要好好体会的。

  注:缩放级别为 180 、90、45、22.5、……

根据视角范围获取缩放级别

        public int GetZoomLevelByTrueViewRange(double trueViewRange)
        {
            
int maxLevel = 3; //视角范围为45度
            
int minLevel = 19;
            
int numLevels = minLevel - maxLevel + 1;
            
int retLevel = maxLevel;
            
for (int i = 0; i < numLevels; i++)
            {
                retLevel 
= i + maxLevel;

                double viewAngle = 180;
                
for (int j = 0; j < i; j++)
                {
                    viewAngle 
= viewAngle / 2.0;
                }
                
if (trueViewRange >= viewAngle)
                {
                    
break;
                }
            }
            
return retLevel;
        }

 

  VE插件最最关键的方法为Update(),因为VE实质上就是不断地根据缩放级别更新影像数据,其实就是构建要渲染绘制的对象集合,最后由Render()方法完成渲染绘制。所以该方法是VE中最复杂的,当然也就是VE的处理精华所在,也就是我们研究学习的重点啦。(说这些,主要是告诉大家这里是重点,要好好研究和关注)

 

Update()代码

        /// <summary>
        
/// Update layer (called from worker thread)
        
/// </summary>
        public override void Update(DrawArgs drawArgs)
        {

            try
            {
                
if (this.isOn == false)
                {
                    
return;
                }

                //NOTE for some reason Initialize is not getting called from the Plugin Menu Load/Unload
                
//it does get called when the plugin loads from Startup
                
//not sure what is going on, so i'll just call it manually
                if (this.isInitialized == false)
                {
                    
this.Initialize(drawArgs);
                    
return;
                }

                //get lat, lon 获取经纬度、倾斜角度、高度等
                double lat = drawArgs.WorldCamera.Latitude.Degrees;
                
double lon = drawArgs.WorldCamera.Longitude.Degrees;
                
double tilt = drawArgs.WorldCamera.Tilt.Degrees;
                
//determine zoom level
                double alt = drawArgs.WorldCamera.Altitude;
                
//could go off distance, but this changes when view angle changes
                
//Angle fov = drawArgs.WorldCamera.Fov; //stays at 45 degress
                
//Angle viewRange = drawArgs.WorldCamera.ViewRange; //off of distance, same as TVR but changes when view angle changes

               //获取当前视角范围      
                Angle tvr = drawArgs.WorldCamera.TrueViewRange; //off of altitude
                
//smallest altitude = 100m
                
//tvr = .00179663198575926
                
//start altitude = 12756273m
                
//tvr = 180

                //WW _levelZeroTileSizeDegrees 获取缩放级别,上面已经介绍啦
                
//180 90 45 22.5 11.25 5.625 2.8125 1.40625 .703125 .3515625 .17578125 .087890625 0.0439453125 0.02197265625 0.010986328125 0.0054931640625
                int zoomLevel = GetZoomLevelByTrueViewRange(tvr.Degrees);

 
         //只要到一定缩放级别时,才启用VE(这里我们可以学习,来控制一些图层的显示)
                
//dont start VE tiles until a certain zoom level
                if (zoomLevel < veForm.StartZoomLevel)
                {
                    
this.RemoveAllTiles();
            
this.ForceRefresh();
                    
return;
                }

                //WW tiles
                
//double tileDegrees = GetLevelDegrees(zoomLevel);
                
//int row = MathEngine.GetRowFromLatitude(lat, tileDegrees);
                
//int col = MathEngine.GetColFromLongitude(lon, tileDegrees);

                //VE tiles
                double metersY;
                
double yMeters;
                
int yMetersPerPixel;
                
int row;
                
/*
                //WRONG - doesn't stay centered away from equator
                //int yMeters = LatitudeToYAtZoom(lat, zoomLevel); //1024
                double sinLat = Math.Sin(DegToRad(lat));
                metersY = earthRadius / 2 * Math.Log((1 + sinLat) / (1 - sinLat)); //0
                yMeters = earthHalfCirc - metersY; //20037508.342789244
                yMetersPerPixel = (int) Math.Round(yMeters / MetersPerPixel(zoomLevel));
                row = yMetersPerPixel / pixelsPerTile;
                
*/
                
//CORRECT
                
//int xMeters = LongitudeToXAtZoom(lon, zoomLevel); //1024

        //计算弧长,(earthRadius 地球半径,DegToRad(lon)经度的度值转弧度值)
        //DegToRad()为角度转弧度的方法,很简单。弧长=半径*弧度值。(这里球体的弧度值认为有负值)
                double metersX = earthRadius * DegToRad(lon); //0
              //因为从西到东算列数,所以要从-180度算列所在的弧度的总长度的      
                double xMeters = earthHalfCirc + metersX; //20037508.342789244
         //获取总的像素数。MetersPerPixel(zoomLevel))是计算每像素代表的米数。(知识点)
                int xMetersPerPixel = (int)Math.Round(xMeters / MetersPerPixel(zoomLevel));
         //获取列数,从西-》东(-180-》180)来算列数
                
int col = xMetersPerPixel / pixelsPerTile;
          
                
//reproject - overrides row above
                
//this correctly keeps me on the current tile that is being viewed
        //使用UV结构体来存放经纬度,然后调用proj.Forward(uvCurrent)实现投影变换
                UV uvCurrent = new UV(DegToRad(lon), DegToRad(lat));
                uvCurrent 
= proj.Forward(uvCurrent);
                metersY 
= uvCurrent.V;
         //这里为啥是“—”??看过该插件原作者的文章才会知道:原来VE的行数是从北向南算的,而WW是从南向北计算行数的。
                yMeters 
= earthHalfCirc - metersY;
         //获取总像素数
                yMetersPerPixel 
= (int)Math.Round(yMeters / MetersPerPixel(zoomLevel));
         //获取行数
                row 
= yMetersPerPixel / pixelsPerTile;
说明:计算行列数,是为了后面获取切片后图片,并将正确的图片作为纹理渲染到正确的位置上,当然这过程还有很多处理,我会一一分析的。
                
//update mesh if VertEx changes
                if (prevVe != World.Settings.VerticalExaggeration)
                {
                    
lock (veTiles.SyncRoot)
                    {
                        VeTile veTile;
                        
for (int i = 0; i < veTiles.Count; i++)
                        {
                            veTile 
= (VeTile)veTiles[i];
                            
if (veTile.VertEx != World.Settings.VerticalExaggeration)
                            {
              //创建网格MESH(稍后重点分析)
                         veTile.CreateMesh(
this.Opacity, World.Settings.VerticalExaggeration);
                            }
                        }
                    }
                }
                prevVe 
= World.Settings.VerticalExaggeration;

                //if within previous bounds and same zoom level, then exit
                if (row == prevRow && col == prevCol && zoomLevel == prevLvl && tilt == preTilt)
                {
                    
return;
                }

                //System.Diagnostics.Debug.WriteLine("CHANGE");

                
lock (veTiles.SyncRoot)
                {
                    VeTile veTile;

           //使之前存放的veTile,标记为“暂时不需要”,这里没有删除
                    
for (int i = 0; i < veTiles.Count; i++)
                    {
                        veTile 
= (VeTile)veTiles[i];
                        veTile.IsNeeded 
= false;
                    }
                }

                //metadata
                ArrayList alMetadata = null;
                
if (veForm.IsDebug == true)
                {
                    alMetadata 
= new ArrayList();
                    alMetadata.Add(
"yMeters " + yMeters.ToString());
                    alMetadata.Add(
"metersY " + metersY.ToString());
                    alMetadata.Add(
"yMeters2 " + yMeters.ToString());
                    alMetadata.Add(
"vLat " + uvCurrent.V.ToString());
                    
//alMetadata.Add("xMeters " + xMeters.ToString());
                    
//alMetadata.Add("metersX " + metersX.ToString());
                    
//alMetadata.Add("uLon " + uvCurrent.U.ToString());
                }
         
//添加目前VeTile(这是重点,稍后分析)
                
//add current tiles first
                AddVeTile(drawArgs, row, col, zoomLevel, alMetadata);
                
//then add other tiles outwards in surrounding circles

          //添加周边的VeTile
                AddNeighborTiles(drawArgs, row, col, zoomLevel, null1);
                AddNeighborTiles(drawArgs, row, col, zoomLevel, 
null2);
                AddNeighborTiles(drawArgs, row, col, zoomLevel, 
null3);
        
// Extend tile grid if camera tilt above some values
                //根据倾斜角度决定是否继续添加相邻Tile单位          
                if(tilt > 45) AddNeighborTiles(drawArgs, row, col, zoomLevel, null4);
                
if(tilt > 60) AddNeighborTiles(drawArgs, row, col, zoomLevel, null5);

                //if(prevLvl > zoomLevel) //zooming out
                
//{
                
//}            

                
lock (veTiles.SyncRoot)
                {
                    VeTile veTile;

            //移除不需要的veTile图片,为啥是现在移除??(思考一下有啥好处)
                    
for (int i = 0; i < veTiles.Count; i++)
                    {
                        veTile 
= (VeTile)veTiles[i];
                        
if (veTile.IsNeeded == false)
                        {
                            veTile.Dispose();
                            veTiles.RemoveAt(i);
                i
--;
                        }
                    }
                }
        //保存当前基本的行列、缩放级别、倾斜角度
                prevRow 
= row;
                prevCol 
= col;
                prevLvl 
= zoomLevel;
        preTilt 
= tilt;
            }
            
catch (Exception ex)
            {
                Utility.Log.Write(ex);
            }

        }

 

 我们来看看AddVeTile(drawArgs, row, col, zoomLevel, alMetadata);方法,因为该方法是AddNeighborTiles(drawArgs, row, col, zoomLevel, null1);方法的基础。


AddVeTile()方法代码

 private void AddVeTile(DrawArgs drawArgs, int row, int col, int zoomLevel, ArrayList alMetadata)
        {
            
//TODO handle column wrap-around
            //haven't had to explicitly handle this yet

            
bool tileFound = false;
            
lock (veTiles.SyncRoot)
            {
                
foreach (VeTile veTile in veTiles)

抱歉!评论已关闭.