基于 ArcGlobe/Globecontrol 三维地理信息系统开发之显示优化

 

基于 ArcGlobe/Globecontrol 三维地理信息系统开发之显示优化
2008年01月03日 星期四 下午 04:25

How to Optimizing the drawing sequence
    
   蒋践 20070709 MSN:[email protected]
[summary]
本文对ArcGlobe/Globecontrol 中的重画方法的优化提出了四种解决方案。对各
自方案的提出背景及其原理进行了简短的解释,同时给出了实例,值得仔细研究!
Implementation of custom layers for ArcGlobe/GlobeControl would
probably be done for real-time applications. Usually, such applications are
required to match a very high standard of performance, whether it is the ability
to display thousands of elements that pertain to one data feed or update
objects in a very high frequency.

Although efficient, caching your symbol inside a display list is not always
enough to get the best performance. The following are the major techniques to
a display    list.

optimize the drawing sequence:

1、 Minimizing the computation load inside DrawImmediate
2 、Scaling and aggregating the drawn symbology
3、 Screening out objects outside of the display viewport
4、 Using a global timer to redraw the display
一一、、Minimizing the computation load inside DrawImmediate
一一、、
Tip:
1、 一个对象可能在一个绘制周期中需要重画多次,如果每一次都去计算地心坐标和
方向将会影响效率。
2、 解决:当我们对某一个对象进行更新时,将其地心坐标和方向保存。
In most cases, the layer’s drawing frequency does not match the
object’s update rate. An object is usually subjected to update every several
drawing cycles. For that reason, calculating the object’s geocentric
coordinate and orientation only when the element gets updated significantly
decreases the computations that must be done on each drawing cycle.(引
出背景)
  
        To draw the object, you will have to cache its calculated geocentric
coordinate and orientation so it can be used inside the DrawImmediate
method.
        为了去画出某个对象,你将首先保存其计算出的地心坐标及其方向,使它能够调
用内部的DrawImmediate 方法。

The following code shows how to minimize the computation load:
  
   [C#]

public bool AddItem(long zipCode, double lat, double lon)
{
   double X = 0.0, Y = 0.0, Z = 0.0;   
   . . .
   DataRow r = m_table.Rows.Find(zipCode);
   if (null != r) //If the record with this ZIP Code already
exists.
   {
     //Cache the item’s geocentric coordinate to save the
calculation inside method
DrawImmediate ().
globeViewUtil.GeographicToGeocentric(lon, lat, 1000.0, out X,
out Y, out Z);
     //Update the record.
     r[3] = lat;
     r[4] = lon;
     r[5] = X;
     r[6] = Y;
     r[7] = Z;

     lock (m_table)
     {
        r.AcceptChanges();
     }
   }
. . .
}
二二、、Scaling and aggregating symbols
二二、、
   Tip:
1、可以通过一定方法计算每一个 symbol 的当前显示 scale、
2、当某一个符号在当前显示小于一定比例的时候,我们将一样简单的符号如点来表示它!
        Usually, one of the first techniques learned in a geographic information
system (GIS) class for drawing spatially enabled data is to use aggregated
symbology (as scale ratio becomes small when zooming out可伸缩性符号). 3D
drawing is no exception. However, in a 3D environment, the scale constantly

varies since there is no reference surface that can calculate a scale accordingly.
Therefore, the distance from the globe’s camera of each object determines a
reference scale.(相对比例由全球幕布 globe camera的距离来决定) The distance from
the object to the camera can be calculated both in geographical units and in
geocentric units.(对象到全球幕布的距离在地心坐标和地理坐标下都可以计算)
  
      The following shows calculating the scale (magnitude) according to the
distance from the camera to the object using geocentric units and taking into
consideration the elevation exaggeration:
这个例子 show通过从幕布到对象的距离(地心坐标下)来计算放大比例,同是考虑到高程的
发达!
    [C#]
double dblObsX, dblObsY, dblObsZ, dMagnitude;
//获取当前全球幕布
ICamera camera = pGlobeViewer.GlobeDisplay.ActiveViewer.Camera;
//Get the camera location in a geocentric coordinate (OpenGL coordsystem).
得到地心坐标系用于OpenGL
camera.Observer.QueryCoords(out dblObsX, out dblObsY);
dblObsZ = camera.Observer.Z;
//QA
IGlobeDisplayRendering globeDisplayRendering =
IGlobeDisplayRendering)m_globeDisplay;
//得到基本的起始放大点
double baseExaggeration = globeDisplayRendering.BaseExaggeration;
//
double X = 0.0, Y = 0.0, Z = 0.0;
X = Convert.ToDouble(rec[5]);
Y = Convert.ToDouble(rec[6]);
Z = Convert.ToDouble(rec[7]);
//
m_ipVector3D.SetComponents(dblObsX - X,
                             dblObsY - Y,
                             dblObsZ - Z);
//
dMagnitude = m_ipVector3D.Magnitude;
//

double scale = dblMagnitude * baseExagFactor;
//当得到的比例比较小时,可以用一个符号表示,只有到达一定比例是才在幕布上 draw一个
full symbol。
       Setting the scale threshold, which determines whether to draw a full symbol
or an aggregated symbol is up to the developer, usually according to a
requirement defined as a geographical distance. In many cases, setting the
threshold is done empirically to get the best balance between the aggregated
symbols and the full symbol that looks best and gives the best performance. The
aggregated symbol can be any type of symbol, cached as a display list or a simple
OpenGL geometry. There is no limit to the number of aggregated symbol levels
that can be set to an object.
  
The following code shows how to set the scale threshold:
    [C#]
//If far away from the object, draw it as a simple OpenGL point.
if(scale > 2.5)
{
     GL.glPointSize(5.0f);
     GL.glColor3ub(0, 255, 0);
     GL.glBegin(GL.GL_POINTS);
        GL.glVertex3f(Convert.ToSingle(X), Convert.ToSingle(Y),
Convert.ToSingle(Z));
     GL.glEnd();
}
else //When close enough, draw the full symbol.
{
    GL.glPushMatrix();
    {
       //Translate and orient the object into place.
       TranslateAndRotate(X,Y, Z, wksSymbolOrientation, dSymbolAngle);
       //Scale the object.
       GL.glScaled(dObjScale, dObjScale, dObjScale);
         //Draw the object.      
       GL.glCallList(m_intMainSymbolDisplayList);
    }
    GL.glPopMatrix();

}
  
三三。。Screening out objects outside the display viewport
三三。。
  

     Tip:
1、可以计算某一个对象是否在我们的展示幕布中、
2、当不在幕布中时,我们就不让他显示
         Drawing dynamic objects usually involves a large amount of computation.
In addition, drawing a complex full-resolution model that contains a large amount
of triangles can take a considerable amount of resources. The camera’s field of
view is limited to a certain degree. Therefore, many objects get drawn despite the
fact they are not visible inside the viewport. Although OpenGL will not draw these
objects, the computation required to draw an object might be quite expensive. For
that reason, screening out objects outside the viewport is a good practice to
preserve resources and make your code more efficient.(为什么要这样做?)
  
       To screen out objects, you need to convert the object’s coordinate into the
window’s coordinate and test whether it is inside the viewport. There are two ways
to convert a coordinate into a window coordinate. You can use the IGlobeViewUtil
interface to convert a coordinate from each of the other coordinate systems used
by globe (geographic or geocentric) into the window system or you can use
OpenGL to convert from a geocentric coordinate into a window coordinate.(怎么
去计算得到是否在当前可是窗体中)
        通过 OpenGL 的方法从地理坐标到地心坐标的转换需要你去获取如下 feed:OpenGL
的投影 matrix,模型 matrix ,及其视角matrix,这些feed 只能在 DrawImmediate方
法 (globe’s custom layer 情况下)及其 IGlobeDisplayEvents::BeforeDraw and
IGlobeDisplayEvents::AfterDraw方法下才能获取。虽然,这种方法比较快速同时精度比
较高并且可以用来估计外部的快速展示面,但是当你转变到选择模式下时,投影 matrix将改
变同时结果将出现计算错误。
      所以:建议尽可能的使用model of ArcObjects和 OpenGL 模式相结合。
     使用方法:当 OpenGL 处于选择模式下时,我们就使用 IGlobeViewUtil 接口来把结果转
变到窗体坐标。其他情况下通通用 OpenGL的模式。
      Calling OpenGL to project a coordinate from geocentric into a window
coordinate system requires you to get from OpenGL the projection matrix, model
matrix, and the viewport matrix. This can only be done inside the DrawImmediate
method (in the case of a globe’s custom layer) or inside
IGlobeDisplayEvents::BeforeDraw and IGlobeDisplayEvents::AfterDraw.
        Although, using OpenGL to convert from geocentric coordinates into window
coordinates is very fast and accurate (it also allows you to screen out objects
outside the clipping planes), when switched into selection mode, the projection
matrix changes and results in erroneous calculations.
  

For that reason, it is possible to use a mixed model of ArcObjects together with
OpenGL to test if an object is inside the viewport. When OpenGL is in selection
mode, use IGlobeViewUtil to convert into a window coordinate and the rest of the
time, use OpenGL.
  
The following code shows testing an object inside the viewport:
    [C#]
private bool InsideViewport(double x, double y, double z, double clipNear,
uint mode)
{
bool inside = true;
     //In selection mode, the projection matrix is changed.
     //Therefore, use the GlobeViewUtil because calling gluProject gives
unexpected results.
     if (GL.GL_SELECT == mode)
     {
       int winX, winY;
       m_globeViewUtil.GeocentricToWindow(x, y, z, out winX, out winY);
      
       inside = (winX >= m_viewport[0] && winX <= m_viewport[2]) &&
                (winY >= m_viewport[1] && winY <= m_viewport[3]);
     }
     else
     {
         //Use gluProject to convert into the window’s coordinate.
        unsafe
        {
           double winx, winy, winz;
           GLU.gluProject(x, y, z,
                          m_modelViewMatrix,
                          m_projMatrix,
                          m_viewport,
                          &winx, &winy, &winz);
           inside = (winx >= m_viewport[0] && winx <= m_viewport[2]) &&

                    (winy >= m_viewport[1] && winy <= m_viewport[3] &&
                    (winz >= clipNear && winz <= 1.0));
          }       
     }
     return inside;
}
   The following code filters out objects outside of the viewport:

[C#]
//Get the OpenGL matrices.
GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX, m_modelViewMatrix);
GL.glGetIntegerv(GL.GL_VIEWPORT, m_viewport);
GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, m_projMatrix);

//Get the OpenGL rendering mode.
uint mode;
unsafe
{
     int m;
     GL.glGetIntegerv(GL.GL_RENDER_MODE, &m);
     mode = (uint)m;
}
//Get the globe’s near clipping plane. The ClipNear value is required
for the viewport filtering
(since we don’t want to draw an item beyond the clipping planes).
IGlobeAdvancedOptions advOpt = m_globeDisplay.AdvancedOptions;

double clipNear = advOpt.ClipNear;

//Ensure the object is within the viewport.
if (!InsideViewport(X, Y, Z, clipNear, mode))
     continue;
//Continue with the drawings here.

. . .
四四、、Using a single timer to redraw the display
四四、、
   Tip:
1、动态效果的制作方法。使用 timer来控制
2、及其需要注意的问题:所以的 customer layer 公用一个timer
      Since the data managed by a custom layer or in custom drawings is usually
dynamic, you need to constantly redraw the globe display to reflect changes to
that data. The redraw frequency should be determined by the amount of time it
takes each object to update, as well as the amount of objects that you have to
address in the application.(引出问题,需要动态效果)
  
       Since Window’s operating system cannot support a true real-time application,
it is usually acceptable to have a delay of a few milliseconds between the time that
an object gets updated and the time that it gets drawn in the globe display.
Therefore, having a timer in your application that constantly redraws the display is
usually the best solution when your data is dynamic. (解决方法:使用 timer)
  
     The timer's elapsed event 通过进程池来引发,所以没有在主线程下执行。
     The timer's elapsed event is raised on a ThreadPool’s thread. This means it is
executed on another thread that is not the main thread. The solution is to treat an
ArcObjects component like a user interface (UI) control by using the Invoke
method to delegate the call to the main thread (where the ArcObjects component
was created) and prevent making cross apartment calls.(原理)
  
    To support the Invoke method, your class needs to implement the
ISynchronizeInvoke .NET interface. Alternatively, your class can inherit from the
System.Windows.Forms.Control .NET interface. This way, your class automatically
supports the Invoke method.(怎么做)
  
The following shows a class inheriting the System.Windows.Forms.Control
interface:
    [C#]
//Class definition.
public abstract class GlobeCustomLayerBase : Control,
                                               ILayer,
                                               IGeoDataset,
                                               . . .
     //Class constructor.
     public GlobeCustomLayerBase()
     {
        //Call CreateControl to create the handle.

        this.CreateControl();
     }

. . .
The following code shows using the timer to redraw the GlobeDisplay:
  

[C#]
//Redraw delegate invokes the timer's event handler on the main thread.
private delegate void RedrawEventHandler();

//Set the layer’s redraw timer.
private System.Timers.Timer m_redrawTimer = null;

//Initialize the redraw timer.
m_redrawTimer = new System.Timers.Timer(200);
m_redrawTimer.Enabled = false;
//Wire the timer’s elapsed event handler.
m_redrawTimer.Elapsed += new
ElapsedEventHandler(OnRedrawUpdateTimer);

//Redraw the timer’s elapsed event handler.
private void OnRedrawUpdateTimer(object sender, ElapsedEventArgs e)
{
     //Since this is the timer’s event handler, it gets executed on a
different thread than the
main one. Therefore, the Invoke call is required to force the call on
the main thread
and prevent cross apartment calls.
     if(m_bTimerIsRunning)
        base.Invoke(new RedrawEventHandler(OnRedrawEvent));
}


//This method invoked by the timer’s elapsed event handler and gets
executed on the
main thread.
void OnRedrawEvent()
{
     m_globeDisplay.RefreshViewers();
}
    译: 另外一种可选方案是当元素更新时,在一次重新画,弊端:当对象很多是不停的重画
将可能把资源全部耗掉。
      Another alternative is to redraw the globe display each time an element gets
updated. However, this approach can lead to a nonstop redraw of the display when
there are a large number of objects and could consume all of the system
resources.
  
总结:当有很多动态图层时,如果每一个图层都给一个 timer,那么,可以会失去控制,所以,
所有的图层公用一个timer。
      When there is more than one dynamic layer, having multiple timers for each
layer can result in a constructive interference of the timers, which may lead to
excessive uncontrolled redrawing of the globe display. For that reason, the best
solution is to implement a common singleton object that serves all layers that
require constant drawing. This way, no matter how many dynamic layers get
added to the globe, the drawing rate remains constant.
  
The following C++ active template library (ATL) class declaration code
shows how the global timer singleton is declared: ((to look for help9.2for
((
engine net))
))
  
   但是没有找到相关的接口但是没有找到相关的接口 Engine9.2 中中
但是没有找到相关的接口但是没有找到相关的接口 中中
The following code to use the global timer class is straightforward:
    [C#]
//Declare the global timer.
private IGlobalTimer m_globalTimer = null;

public void DrawImmediate(IGlobeViewer pGlobeViewer)
{
     if(!m_bVisible || !m_bValid)
        return;
     . . .


     if(null == m_globalTimer)
     {
        m_globalTimer = new GlobalTimerClass();
        m_globalTimer.GlobeDisplay = pGlobeViewer.GlobeDisplay;
        if(m_globalTimer.RedrawInterval > 200)
           m_globalTimer.RedrawInterval = 200;
        m_globalTimer.Start();
     }
     . . .

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1763362

你可能感兴趣的:(arc)