osgEarth示例分析——osgearth_terrainprofile

前言

osgearth_terrainprofile示例,涉及到一个新的类 TerrainProfileCalculator(地形轮廓计算器类),用来计算两个点连线之间的地形数据。左下角会根据点击的起点和终点进行计算,并更新显示地形信息。

效果

拖动地球,到某一个视角下,左下角的地形线会跟着变化。点击地球上的某处,控制台会输出将经纬高的信息。

拖动地球、缩放地球、点击地球,都会打印下面的内容,但并未找到是哪段程序输出的以下内容:

......
start_lat = 0.829058 start_longitude = 1.50569 start_height = 764.592
end_lat = 0.809018 end_longitude = 1.62256 end_height = 2756.64
start_lat = 0.804757 start_longitude = 1.90578 start_height = 1089.97
end_lat = 1.00064 end_longitude = 1.82342 end_height = 334.665
start_lat = 0.804757 start_longitude = 1.90578 start_height = 1089.97
end_lat = 1.00064 end_longitude = 1.82342 end_height = 307.551
start_lat = 0.749422 start_longitude = 1.89211 start_height = 948.917
end_lat = 0.616939 end_longitude = 1.93604 end_height = 416.177
......

代码分析

1. osg::Camera* createHud(double width, double height); 创建一个HUD相机,位置为左下角。

2. class TerrainProfileGraph 类,绘制地形的剖面高度线
// 组装将要展示在HUD相机内的东西。
redraw() {
// 1. 创建左下角背景节点
createBackground();
// 2. 绘制背景上的线条
// 3. 将4个label也绘制上去
}

3. class DrawProfileEventHandler类,处理鼠标点击事件。
点击起点和终点后,地形轮廓计算器对象_profileCalculator会更新,进而左下角的图线、标签也会更新。
看代码的意思,地球上应该会出现两个点的连线,但是并没有绘制上去。

#include <osg/Notify>
#include <osgGA/StateSetManipulator>
#include <osgGA/GUIEventHandler>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgEarth/MapNode>
#include <osgEarth/XmlUtils>
#include <osgEarthUtil/EarthManipulator>
#include <osgEarthUtil/AutoClipPlaneHandler>
#include <osgEarthUtil/TerrainProfile>
#include <osgEarth/GeoMath>
#include <osgEarth/Registry>
#include <osgEarth/FileUtils>
#include <osgEarth/GLUtils>
#include <osgEarthFeatures/Feature>
#include <osgEarthAnnotation/FeatureNode>
#include <osgText/Text>
#include <osgText/Font>
#include <osg/io_utils>

using namespace osgEarth;
using namespace osgEarth::Util;
using namespace osgEarth::Symbology;
using namespace osgEarth::Features;
using namespace osgEarth::Annotation;


//Creates a simple HUD camera
// 创建一个HUD相机,放在左下角
osg::Camera* createHud(double width, double height)
{
    osg::Camera* hud = new osg::Camera;
    hud->setProjectionMatrix(osg::Matrix::ortho2D(0,width,0,height));// 左下角,2维投影    
    hud->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
    hud->setViewMatrix(osg::Matrix::identity());    // 更新
    hud->setClearMask(GL_DEPTH_BUFFER_BIT);// 设置深度缓存
    hud->setRenderOrder(osg::Camera::POST_RENDER);
    hud->setAllowEventFocus(false);
    osg::StateSet* hudSS = hud->getOrCreateStateSet();
    GLUtils::setLighting(hudSS, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
    hudSS->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF);
    hudSS->setMode( GL_BLEND, osg::StateAttribute::ON);

    return hud;
}

/**
 * Simple terrain profile display
 * 简单的地形剖面显示。
 */
class TerrainProfileGraph : public osg::Group
{
public:
    /*
     * Callback that is fired when the TerrainProfile changes
	 * 当TerrainProfile更改时触发的回调。
     */
    struct GraphChangedCallback : public TerrainProfileCalculator::ChangedCallback
    {
        GraphChangedCallback( TerrainProfileGraph* graph):
        _graph( graph )
      {
      }

      virtual void onChanged(const TerrainProfileCalculator* sender )
      {
        _graph->setTerrainProfile( sender->getProfile() );
      }

      TerrainProfileGraph* _graph;
    };

	// 构造时,传入 地形轮廓计算器对象profileCalculator,同时创建4个标签(仅设置位置、颜色、字体大小)
    TerrainProfileGraph( TerrainProfileCalculator* profileCalculator, double graphWidth = 200, double graphHeight = 200 ):
    _profileCalculator( profileCalculator ),
        _graphWidth( graphWidth ),
        _graphHeight( graphHeight ),
        _color( 1.0f, 1.0f, 0.0f, 1.0f),
        _backcolor(0.0f,0.0f,0.0f,0.5f)
    {
        _graphChangedCallback = new GraphChangedCallback( this );
        _profileCalculator->addChangedCallback( _graphChangedCallback.get() );

        float textSize = 8;
        osg::ref_ptr< osgText::Font> font = osgEarth::Registry::instance()->getDefaultFont();

        osg::Vec4 textColor = osg::Vec4f(1,0,0,1);
        
		// 显示距离最小的标签,在左下角
        _distanceMinLabel = new osgText::Text();
        _distanceMinLabel->setCharacterSize( textSize );
        _distanceMinLabel->setFont( font.get() );
        _distanceMinLabel->setAlignment(osgText::TextBase::LEFT_BOTTOM);
        _distanceMinLabel->setColor(textColor);

		// 显示距离最大的标签,在右下角
        _distanceMaxLabel = new osgText::Text();
        _distanceMaxLabel->setCharacterSize( textSize );
        _distanceMaxLabel->setFont( font.get() );
        _distanceMaxLabel->setAlignment(osgText::TextBase::RIGHT_BOTTOM);
        _distanceMaxLabel->setColor(textColor);

		// 显示最小高程的标签,在右下角
        _elevationMinLabel = new osgText::Text();
        _elevationMinLabel->setCharacterSize( textSize );
        _elevationMinLabel->setFont( font.get() );
        _elevationMinLabel->setAlignment(osgText::TextBase::RIGHT_BOTTOM);
        _elevationMinLabel->setColor(textColor);

		// 显示最大高程的标签,在右上角
        _elevationMaxLabel = new osgText::Text();
        _elevationMaxLabel->setCharacterSize( textSize );
        _elevationMaxLabel->setFont( font.get() );
        _elevationMaxLabel->setAlignment(osgText::TextBase::RIGHT_TOP);
        _elevationMaxLabel->setColor(textColor);
    }

	// 析构时,需要移除回调方法
    ~TerrainProfileGraph()
    {
        _profileCalculator->removeChangedCallback( _graphChangedCallback.get() );
    }

	// 设置 地形轮廓计 对象
    void setTerrainProfile( const TerrainProfile& profile)
    {
        _profile = profile;// 存储地形轮廓计算结果
        redraw();// 绘制
    }

    //Redraws the graph 绘制(或者叫组装)矩形,包括:背景、线、4个标签的内容
    void redraw()
    {
        removeChildren( 0, getNumChildren() );

		// 将创建的背景节点,加入当前节点
        addChild( createBackground( _graphWidth, _graphHeight, _backcolor));

        osg::Geometry* geom = new osg::Geometry;
        geom->setUseVertexBufferObjects(true);

        osg::Vec3Array* verts = new osg::Vec3Array();
        verts->reserve( _profile.getNumElevations() );
        geom->setVertexArray( verts );// 绑定顶点
        if ( verts->getVertexBufferObject() )
            verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);// 静态绘制

		// 绑定颜色
        osg::Vec4Array* colors = new osg::Vec4Array(osg::Array::BIND_OVERALL);
        colors->push_back( _color );
        geom->setColorArray( colors );

		// 从 地形轮廓计算器对象 获取最小高程和最大高程
        double minElevation, maxElevation;
        _profile.getElevationRanges( minElevation, maxElevation );
        double elevationRange = maxElevation - minElevation;// 高程范围

        double totalDistance = _profile.getTotalDistance();// 获取总的距离

        for (unsigned int i = 0; i < _profile.getNumElevations(); i++)
        {
            double distance = _profile.getDistance( i );
            double elevation = _profile.getElevation( i );

			// 根据 距离和高程 占 总距离和总高程 的比,确定xy坐标
            double x = (distance / totalDistance) * _graphWidth;
            double y = ( (elevation - minElevation) / elevationRange) * _graphHeight;
            verts->push_back( osg::Vec3(x, y, 0 ) );// 用此值来初始化顶点坐标
        }

		// 用计算得到的顶点坐标绘制连续线条
        geom->addPrimitiveSet( new osg::DrawArrays( GL_LINE_STRIP, 0, verts->size()) );
        osg::Geode* graphGeode = new osg::Geode;
        graphGeode->addDrawable( geom );
        addChild( graphGeode );

		// 添加4个label,并初始化文本内容
        osg::Geode* labelGeode =new osg::Geode;
        labelGeode->addDrawable( _distanceMinLabel.get());
        labelGeode->addDrawable( _distanceMaxLabel.get());
        labelGeode->addDrawable( _elevationMinLabel.get());
        labelGeode->addDrawable( _elevationMaxLabel.get());

        _distanceMinLabel->setPosition( osg::Vec3(0,0,0));
        _distanceMaxLabel->setPosition( osg::Vec3(_graphWidth-15,0,0));
        _elevationMinLabel->setPosition( osg::Vec3(_graphWidth-5,10,0));
        _elevationMaxLabel->setPosition( osg::Vec3(_graphWidth-5,_graphHeight,0));
        
        _distanceMinLabel->setText("0m");        
        _distanceMaxLabel->setText(toString<int>((int)totalDistance) + std::string("m"));
        
        _elevationMinLabel->setText(toString<int>((int)minElevation) + std::string("m"));
        _elevationMaxLabel->setText(toString<int>((int)maxElevation) + std::string("m"));

        addChild( labelGeode );

    }

	// 创建左下角的背景
    osg::Node* createBackground(double width, double height, const osg::Vec4f& backgroundColor)
    {
        //Create a background quad
        osg::Geometry* geometry = new osg::Geometry();
        geometry->setUseVertexBufferObjects(true);

		// 左下角为(0,0)点位置
        osg::Vec3Array* verts = new osg::Vec3Array();
        verts->reserve( 4 );
        verts->push_back( osg::Vec3(0,0,0));
        verts->push_back( osg::Vec3(width,0,0));
        verts->push_back( osg::Vec3(width,height,0));
        verts->push_back( osg::Vec3(0,height,0));
        geometry->setVertexArray( verts );// 设置顶点坐标
        if ( verts->getVertexBufferObject() )
            verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);// 静态绘制,因为左下角的矩形背景不会动

        osg::Vec4Array* colors = new osg::Vec4Array(osg::Array::BIND_OVERALL);
        colors->push_back( backgroundColor );// 设置背景色
        geometry->setColorArray( colors );

		// 绘制方式为四边形
        geometry->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 4 ) );

        osg::Geode* geode = new osg::Geode;
        geode->addDrawable( geometry );

        return geode;

    }


    osg::ref_ptr<osgText::Text> _distanceMinLabel, _distanceMaxLabel, _elevationMinLabel, _elevationMaxLabel;

    osg::Vec4f _backcolor;
    osg::Vec4f _color;
    TerrainProfile _profile;
    osg::ref_ptr< TerrainProfileCalculator > _profileCalculator;
    double _graphWidth, _graphHeight;
    osg::ref_ptr< GraphChangedCallback > _graphChangedCallback;
};

/*
 * Simple event handler that draws a line when you click two points with the left mouse button
 * 简单的事件处理程序,当您用鼠标左键单击两点时绘制一条线。
 */
class DrawProfileEventHandler : public osgGA::GUIEventHandler
{
public:
    DrawProfileEventHandler(osgEarth::MapNode* mapNode, osg::Group* root, TerrainProfileCalculator* profileCalculator):
      _mapNode( mapNode ),
          _root( root ),
          _startValid( false ),
          _profileCalculator( profileCalculator )
      {
          _start = profileCalculator->getStart().vec3d();
          _end = profileCalculator->getEnd().vec3d();
          compute();
      }

      bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
      {
		  // 鼠标左键按下,拾取点
          if (ea.getEventType() == ea.PUSH && ea.getButton() == osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
          {
              osg::Vec3d world;// 存储世界坐标系下的坐标点,XY是屏幕坐标点
              if ( _mapNode->getTerrain()->getWorldCoordsUnderMouse( aa.asView(), ea.getX(), ea.getY(), world ))
              {
                  GeoPoint mapPoint;
                  mapPoint.fromWorld( _mapNode->getMapSRS(), world );// 世界坐标转为经纬度坐标
                  //_mapNode->getMap()->worldPointToMapPoint( world, mapPoint );

                  if (!_startValid)// 判断是否为起点
                  {
                      _startValid = true;
                      _start = mapPoint.vec3d();
                      if (_featureNode.valid())
                      {
                          _root->removeChild( _featureNode.get() );
                          _featureNode = 0;
                      }
                  }
                  else
                  {
                      _end = mapPoint.vec3d();
                      compute();// 获取到终点后,开始计算
                      _startValid = false;                    
                  }
              }        
          }
          return false;
      }

	  // 获取起点和终点后,计算
      void compute()
      {
          //Tell the calculator about the new start/end points
		  // 将起点和终点设置到计算器中
          _profileCalculator->setStartEnd( GeoPoint(_mapNode->getMapSRS(), _start.x(), _start.y()),
                                           GeoPoint(_mapNode->getMapSRS(), _end.x(), _end.y()) );
		  // 如果有,则移除特征
          if (_featureNode.valid())
          {
              _root->removeChild( _featureNode.get() );
              _featureNode = 0;
          }

		  // 新建特征线
          LineString* line = new LineString();
          line->push_back( _start );
          line->push_back( _end );
          Feature* feature = new Feature(line, _mapNode->getMapSRS());
          feature->geoInterp() = GEOINTERP_GREAT_CIRCLE; // 大圆弧线   

          //Define a style for the line
          Style style;
          LineSymbol* ls = style.getOrCreateSymbol<LineSymbol>();
          ls->stroke()->color() = Color::Yellow;// 线颜色
          ls->stroke()->width() = 3.0f;// 线宽
          ls->tessellationSize()->set(100.0, Units::KILOMETERS);// 线的细化程度

          AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
          alt->clamping() = alt->CLAMP_TO_TERRAIN;// 贴地形
          alt->technique() = alt->TECHNIQUE_DRAPE;

          RenderSymbol* render = style.getOrCreate<RenderSymbol>();
          render->lighting() = false;// 关闭灯光

          feature->style() = style;
          _featureNode = new FeatureNode( feature );
          _featureNode->setMapNode(_mapNode);
          _root->addChild( _featureNode.get() );

      }




      osgEarth::MapNode* _mapNode;      
      osg::Group* _root;
      TerrainProfileCalculator* _profileCalculator;// 地形轮廓计算器
      osg::ref_ptr< FeatureNode > _featureNode;
      bool _startValid;
      osg::Vec3d _start;
      osg::Vec3d _end;  
};




int
main(int argc, char** argv)
{
    osg::ArgumentParser arguments(&argc,argv);
    osgViewer::Viewer viewer(arguments);

    // load the .earth file from the command line. 加载earth文件
    osg::ref_ptr<osg::Node> earthNode = osgDB::readNodeFiles( arguments );
    if (!earthNode.valid())
    {
        OE_NOTICE << "Unable to load earth model" << std::endl;
        return 1;
    }

    osg::Group* root = new osg::Group();

    osgEarth::MapNode * mapNode = osgEarth::MapNode::findMapNode( earthNode.get() );
    if (!mapNode)
    {
        OE_NOTICE << "Could not find MapNode " << std::endl;
        return 1;
    }

	// 添加操作器
    osgEarth::Util::EarthManipulator* manip = new EarthManipulator();    
    viewer.setCameraManipulator( manip );

    root->addChild( earthNode );    

	// 主窗口宽高
    double backgroundWidth = 500;
    double backgroundHeight = 500;

	// 左下角小窗口宽高
    double graphWidth = 200;
    double graphHeight = 100;

    //Add the hud 创建hud相机
    osg::Camera* hud = createHud( backgroundWidth, backgroundHeight );
    root->addChild( hud );

	// TerrainProfileCalculator类:计算两点之间的地形轮廓。监视场景图形中高程的更改并更新轮廓。
	// calculator:简称 地形轮廓计算器
	// 两点坐标已知
    osg::ref_ptr< TerrainProfileCalculator > calculator = new TerrainProfileCalculator(mapNode, 
        GeoPoint(mapNode->getMapSRS(), -124.0, 40.0),
        GeoPoint(mapNode->getMapSRS(), -75.1, 39.2)
        );    

	// 地形轮廓节点,存储的是左下角整个矩形内部的节点
    osg::Group* profileNode = new TerrainProfileGraph( calculator.get(), graphWidth, graphHeight );
    hud->addChild( profileNode );// 所有东西放入hud相机


    viewer.getCamera()->addCullCallback( new AutoClipPlaneCullCallback(mapNode));

	// 设置自定义的事件处理器,处理地球上显示的两个点的连线
    viewer.addEventHandler( new DrawProfileEventHandler( mapNode, mapNode, calculator.get() ) );

    viewer.setSceneData( root );    

    // add some stock OSG handlers:
    viewer.addEventHandler(new osgViewer::StatsHandler());
    viewer.addEventHandler(new osgViewer::WindowSizeHandler());
    viewer.addEventHandler(new osgViewer::ThreadingHandler());
    viewer.addEventHandler(new osgViewer::LODScaleHandler());
    viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));

    return viewer.run();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值