基于osgEarth的简单数字地球
TIFF文件
- TIFF为图像格式,以tif为后缀,是一种位图格式。
- 同BMP一样,包含文件头、信息头、数据区。
- 地球纹理和高程图用到了TIFF。
- 用Global Mapper打开高程图可导出等高线。
Shape文件
- Shape文件是以shp为后缀的二进制文件。
- 用arcview打开可以看到其信息为点线面的几何图形,还包括几何图形的坐标,同样也是一张数据表。
OSG文件
- OSG文件是以osg后缀的文本文件。
- 使用
osgviewer *.osg
可快速浏览osg文件定义的模型。 - OSG文件内规定了模型的节点层次,并设置了模型的属性,给出了模型各个顶点的坐标。
earth文件
-
earth文件本质是XML文本文件,后缀为earth。
-
map
:文件的最外层标签,可认为是一个OSG节点,在代码中是osgEarth::MapNode
。- type:给出使用的坐标,我设置为球心坐标
geocentric
,若平面投影为Projected
。 - version:osgEarth的大版本,我用的2.10,标记为
2
。 - name:map的名称。
- type:给出使用的坐标,我设置为球心坐标
-
heightfield
定义高程数据。- name:高程数据名称。
- driver:驱动。
- url:高程数据路径,可以是本地或网络,我在本地保存了一份30m的高程图,格式为.tif。
-
image
定义纹理,可以理解为一个图层。- name:高程数据名称。
- driver:驱动。
- url:高程数据路径,可以是本地或网络。
-
cache
数据量大,加载慢,因此需要设置缓存。- type:缓存类型,我设置filesystem,即本地文件系统。
- path:缓存路径。
1 | <map name="globe" type="geocentric" version="2"> |
OSG基本设置
-
图形显示设置
-
将OSG图形嵌入MFC单文档程序,用Traits类设置OSG窗口属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 获取窗口的矩形区域
::GetWindowRect(hWnd, &rect);
// 创建窗口特征类
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
// 获取windata
osg::ref_ptr<osg::Referenced> windata = new osgViewer::GraphicsWindowWin32::WindowData(hWnd);
// 设置显示的各个属性
traits->x = 0;
traits->y = 0;
traits->width = rect.right - rect.left;
traits->height = rect.bottom - rect.top;
// 双缓冲
traits->doubleBuffer = true;
// 共享的图形环境null
traits->sharedContext = nullptr;
// 继承窗口的像素格式
traits->setInheritedWindowPixelFormat = true;
traits->inheritedWindowData = windata; -
将traits作为参数可构造OSG的图形上下文,并进而设置摄像头、视口、投影。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 创建上下文
osg::GraphicsContext* gc = osg::GraphicsContext::createGraphicsContext(traits);
// 创建摄像头
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc);
// 设置视口
camera->setViewport(new osg::Viewport(traits->x, traits->y, traits->width, traits->height));
// 设置视角
camera->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(traits->width) / static_cast<double>(traits->height), 1.0, 1000.0);
// 设置摄像头
viewer->setCamera(camera);
// 设置场景
viewer->setSceneData(sceneRoot);
// 激活窗口并关联线程
viewer->realize();
-
-
osgEarth操作器
-
将操作器的操作节点设置为mapNode,即地球。
1
2
3
4
5earthManipulator = new osgEarth::Util::EarthManipulator;
if (mapNode.valid())
{
earthManipulator->setNode(mapNode);
} -
设置监视器的相机操作器。
1
viewer->setCameraManipulator(earthManipulator);
-
设置视点变换的平滑过渡。
1
2earthManipulator->getSettings()->setArcViewpointTransitions(true);
earthManipulator->setViewpoint(osgEarth::Viewpoint(nullptr, 112.44, 33.75, 404.06, -3.55, -84.59, 22935594.99), 5);
-
OSG节点树
-
定义
osg::ref_ptr<osg::Group> sceneRoot
为根节点。 -
用
osgDB::readNodeFile
读入.earth文件,作为一个节点,并将其向派生类转换为osgEarth特有的地球节点。mapNode包含坐标系信息、osgEarth定义的地图的指针等信息,通过mapNode可得到.earth文件中的信息,即省界线、国界线、高清纹理等图层。1
2
3
4
5
6
7
8
9
10
11void COSGObject::initSceneGraph()
{
// 创建根节点对象
sceneRoot = new osg::Group;
// 读取影像
osg::ref_ptr<osg::Node> mp = osgDB::readNodeFile("./earthfile/DigitalMap.earth");
// 添加子节点
sceneRoot->addChild(mp);
// 父类向子类转换
mapNode = dynamic_cast<osgEarth::MapNode*>(mp.get());
} -
星空节点:SkyNode类生成,直接连向根节点。
1
2
3
4
5
6
7
8
9
10void COSGObject::initSky()
{
// 初始化天空
osg::ref_ptr<osgEarth::Util::SkyNode> skyNode = osgEarth::Util::SkyNode::create(mapNode);
skyNode->setDateTime(osgEarth::DateTime(2021, 12, 11, 15));
skyNode->attach(viewer, 1);
skyNode->setStarsVisible(true);
skyNode->setSunVisible(true);
sceneRoot->addChild(skyNode);
} -
国旗节点:读取国旗图像作为节点,直接连向根节点。
1
2
3
4
5
6
7
8
9
10
11void COSGObject::initNationFlag()
{
// 初始化国旗标识
nationFlag = new osg::Group;
sceneRoot->addChild(nationFlag);
osg::Image* chinaIcon = osgDB::readImageFile("data/label/chinaYES.png");
osg::ref_ptr<osgEarth::Annotation::PlaceNode> placeNode = new osgEarth::Annotation::PlaceNode;
placeNode->setIconImage(chinaIcon);
placeNode->setPosition(osgEarth::GeoPoint(mapNode->getMapSRS()->getGeographicSRS(), 110, 34));
nationFlag->addChild(placeNode.get());
} -
三维模型(cow.osg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42void COSGObject::init3DCow()
{
// 得到世界坐标下的转换矩阵
osg::Matrixd matrix;
mapNode->getMapSRS()->getGeocentricSRS()->getEllipsoid()->computeLocalToWorldTransformFromLatLongHeight(osg::DegreesToRadians(30.58), osg::DegreesToRadians(119.99), 10, matrix);
// 比例变换——放大一下
matrix.preMult(osg::Matrixd::scale(osg::Vec3d(29, 25, 25)));
// transform节点
scenicSpot = new osg::MatrixTransform;
scenicSpot->setMatrix(matrix);
// 设置光照
osg::ref_ptr<osg::StateSet> state = scenicSpot->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::ON);
state->setMode(GL_LIGHT0, osg::StateAttribute::ON);
state->setMode(GL_LIGHT1, osg::StateAttribute::ON);
osg::ref_ptr<osg::LightSource> lightSource0 = new osg::LightSource;
osg::ref_ptr<osg::Light>light0 = new osg::Light;
light0->setLightNum(0);
light0->setDirection(osg::Vec3(0, -1, 0));
light0->setPosition(osg::Vec4(-4817129, -5391926, -141700, 0));
light0->setDiffuse(osg::Vec4(1.0, 1.0, 0.8, 1));
lightSource0->setLight(light0);
osg::ref_ptr<osg::LightSource> lightSource1 = new osg::LightSource;
osg::ref_ptr<osg::Light>light1 = new osg::Light;
light1->setLightNum(0);
light1->setDirection(osg::Vec3(0, 1, 0));
light1->setPosition(osg::Vec4(-4817129, 5391926, -141700, 0));
light1->setDiffuse(osg::Vec4(1.0, 1.0, 0.9, 1));
lightSource1->setLight(light1);
// 读取osg文件
osg::ref_ptr<osg::Node>cow = osgDB::readNodeFile("./data/model/cow.osg");
// osg中光照只会对有法线的模型起作用,而模型经过缩放后法线不变。
// 所以需要手动设置属性,让法线随着模型大小变化而变化。
// If enabled, normal vectors specified with glNormal are scaled to unit length after transformation.
cow->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, osg::StateAttribute::ON);
scenicSpot->addChild(cow);
scenicSpot->addChild(lightSource0);
scenicSpot->addChild(lightSource1);
sceneRoot->addChild(scenicSpot);
}
操作osgEarth的元素
-
earth文件的图层
-
osgEarth提供了ImageLayer来操作图层。
-
通过mapNode获取earth文件信息。
1
2
3
4
5
6
7
8
9void COSGObject::initLayer()
{
// 从earth文件中根据name获得图层
globeLayer = mapNode->getMap()->getLayerByName<osgEarth::ImageLayer>("globeImage");
provinceBoundaryLayer = mapNode->getMap()->getLayerByName<osgEarth::ImageLayer>("provinceBoundary");
countryBoundaryLayer = mapNode->getMap()->getLayerByName<osgEarth::ImageLayer>("countryBoundary");
contourLayer = mapNode->getMap()->getLayerByName<osgEarth::ImageLayer>("contour");
detailLayer = mapNode->getMap()->getLayerByName<osgEarth::ImageLayer>("detail");
} -
用ImageLayer对象操作图层的属性,如透明度。
1
2
3
4
5
6
7
8
9
10
11void COSGObject::setProvinceBoundaryOpacity(float opacity)
{
if (provinceBoundaryLayer.valid())
{
provinceBoundaryLayer->setOpacity(opacity);
}
}
void COSGObject::enableProvinceBoundary(bool flag)
{
provinceBoundaryLayer->setVisible(flag);
}
-
-
OSG节点的显示
-
所有节点都继承自Node类。
-
将NodeMask设置为0可让节点及其子节点不可见。
1
2
3
4void COSGObject::enableNationFlag(BOOL flag)
{
nationFlag->getChild(0)->setNodeMask(flag);
}
-
坐标计算
-
经纬度
-
调用Viewer类的computeIntersections函数,求摄像头与焦点连线与地球的交点。
-
得到交点后转换为经纬度并显示在编辑框中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 求交点
if (viewer->computeIntersections(eventAdapter, res))
{
osgUtil::LineSegmentIntersector::Intersection first = *res.begin();
// 取出交点坐标
osg::Vec3d point = first.getWorldIntersectPoint();
double latitude, longitude, altitude;
// 转经纬度
osg::ref_ptr<osg::EllipsoidModel> ellipsoidModel = new osg::EllipsoidModel;
ellipsoidModel->convertXYZToLatLongHeight(point.x(), point.y(), point.z(), latitude, longitude, altitude);
latitude = osg::RadiansToDegrees(latitude);
longitude= osg::RadiansToDegrees(longitude);
CString str;
str.Format(_T("%.2lf"), longitude);
latitudeEdit->SetEditText(str);
str.Format(_T("%.2lf"), latitude);
longitudeEdit->SetEditText(str);
str.Format(_T("%.2lfm"), altitude);
altitudeEdit->SetEditText(str);
} -
-
视点
-
通过Viewer得到操作器,再获取当前视点。
-
视点的focalPoint即为焦点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 获取视点
osgEarth::Viewpoint viewpoint = dynamic_cast<osgEarth::Util::EarthManipulator*>(viewer->getCameraManipulator())->getViewpoint();
CString str;
osgEarth::GeoPoint pos = viewpoint.focalPoint().value();
str.Format(_T("%.2lf"), pos.x());
viewLongitudeEdit->SetEditText(str);
str.Format(_T("%.2lf"), pos.y());
viewLatitudeEdit->SetEditText(str);
str.Format(_T("%.2lf"), pos.z());
viewZEdit->SetEditText(str);
str.Format(_T("%.2lf"), viewpoint.getHeading());
viewHeadingEdit->SetEditText(str);
str.Format(_T("%.2lf"), viewpoint.getPitch());
viewPitchEdit->SetEditText(str);
str.Format(_T("%.2lf"), viewpoint.getRange());
viewRangeEdit->SetEditText(str); -