前言
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();
}