【软件工程作业】简单数字地球_哔哩哔哩_bilibili

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的名称。
  • heightfield定义高程数据。

    • name:高程数据名称。
    • driver:驱动。
    • url:高程数据路径,可以是本地或网络,我在本地保存了一份30m的高程图,格式为.tif。
  • image定义纹理,可以理解为一个图层。

    • name:高程数据名称。
    • driver:驱动。
    • url:高程数据路径,可以是本地或网络。
  • cache数据量大,加载慢,因此需要设置缓存。

    • type:缓存类型,我设置filesystem,即本地文件系统。
    • path:缓存路径。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<map name="globe" type="geocentric" version="2">
<!-- 定义全球影像-->
<image name="globeImage" driver="gdal">
<url>../data/globe/globe.tif</url>
</image>
<!-- 全球高程-->
<heightfield name="globeHeightfiled" driver="gdal">
<url>../data/heightfield/30m.tif</url>
</heightfield>
<image name="detail" driver="composite">
<!-- 局部高清纹理 复合影像(消除接缝、没有image数量限制)-->
<image name="zhejiangImage" driver="gdal">
<url>../data/detail/zhejiang.tif</url>
</image>
<image name="deqingImage" driver="gdal">
<url>../data/detail/deqing.tif</url>
</image>
<image name="plateauImage" driver="gdal">
<url>../data/detail/plateau.tif</url>
</image>
</image>

<!-- 国界线-->
<!-- agglite 将矢量文件栅格化 -->
<image name="countryBoundary" driver="agglite">
<!--子标签读取shape(画形状、贴图) ogr将shape换成纹理叠加到图上 -->
<features name="world" driver="ogr">
<url>../data/shpFile/world.shp</url>
<!-- 建立空间索引(加快加载速度) -->
<build_spatial_index>true</build_spatial_index>
</features>
<!-- 栅格化的类型(点线面) -->
<geomerty_type>line</geomerty_type>
<!-- 以像素为单位 -->
<relative_line_size>true</relative_line_size>
<!-- css设置风格 -->
<styles>
<style type="text/css">
world {
stroke: #ffff00;
stroke-opacity: 1.0;
stroke-width: 2.0;
}
</style>
</styles>
</image>
<!-- 省界线 -->
<image name="provinceBoundary" driver="agglite">
<features name="china" driver="ogr">
<url>../data/shpFile/china.shp</url>
<build_spatial_index>true</build_spatial_index>
</features>
<geomerty_type>line</geomerty_type>
<relative_line_size>true</relative_line_size>
<styles>
<style type="text/css">
china {
stroke: #ffffff;
stroke-opacity: 1.0;
stroke-width: 1.5;
}
</style>
</styles>
</image>
<!-- 等高线 -->
<image name="contour" driver="agglite">
<features name="contour" driver="ogr">
<url>../data/shpFile/plateau.shp</url>
<build_spatial_index>true</build_spatial_index>
</features>
<geomerty_type>line</geomerty_type>
<relative_line_size>true</relative_line_size>
<styles>
<style type="text/css">
china {
stroke: #000000;
stroke-opacity: 1.0;
stroke-width: 2;
}
</style>
</styles>
</image>
<!-- 文件缓存-->
<options>
<cache type="filesystem">
<path>./FileCache</path>
</cache>
</options>
</map>

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
      5
      earthManipulator = new osgEarth::Util::EarthManipulator;
      if (mapNode.valid())
      {
      earthManipulator->setNode(mapNode);
      }
    • 设置监视器的相机操作器。

      1
      viewer->setCameraManipulator(earthManipulator);
    • 设置视点变换的平滑过渡。

      1
      2
      earthManipulator->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
    11
    void 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
    10
    void 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
    11
    void 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
    42
    void 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
      9
      void 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
      11
      void 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
      4
      void 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);