osgEarth示例分析——osgearth_cluster

news2024/12/25 1:03:56

前言

osgearth_cluster示例,展示了绘制很多模型时,如何合适设置模型遮挡的混乱情况。

当模型过多时,可以创建 osgEarth::Util::ClusterNode 节点对象,然后创建 osg::NodeList,将需要绘制的节点 node 们,都 push_back 到 osg::NodeList 中,然后将 osg::NodeList 添加 addChild 到 osgEarth::Util::ClusterNode 中,通过控制 osgEarth::Util::ClusterNode 的半径范围,来控制节点node们的显示密集程度。

执行效果

执行命令如下:

// 简单的earth文件即可
osgearth_clusterd.exe earth_image\world.earth

绘制10000个飞机和牛的模型,并且为其添加icon图标。当距离较远时,会显示图标。拉进后,才现实模型。

 Enabled的复选框被取消勾选后,图上的模型、图标、文本,都会消失,取而代之的是culler,像玻璃碎渣。

 当扩大半径值时,所有的模型看起来稀疏了不少。但数量并没有改变。只是距离相近的相同的模型用1个图标显示,并且文本中会显示当前标签下,有多少个模型。类似地图中显示的那样。视觉上更友好些。

 Enabled的下方灰色矩形是按钮(自带按钮确实丑了点)。点击按钮,会随机生成很多PlaceNode。这些黄色的图标,也会随着视距增加,合并成一个小的图标,比如飞机或者医院的图标,然后显示当前隐藏的黄色图标的个数。

 代码分析

额外先分析一下坐标系,毕竟经常会用到。

// 通过mapNode获取到空间坐标系类
osgEarth::SpatialReference* geoSRS = mapNode->getMapSRS();

// 在 osgEarth::SpatialReference 类中,有几种获取坐标系的方式:

// 地理坐标系,一般高度。
getGeographicSRS();

// 大地测量坐标系,获取内容同上,但是z值为:大地椭球之上的z值.
// 当存在地形数据时,此方法用的较多。
getGeodeticSRS();

// 获取与此SRS椭球体关联的地心参考系。代码中,用的较少
getGeocentricSRS();

一、性质不同

1、地理坐标系(GeographicCoordinateSystem),是使用三维球面来定义地球表面位置,以实现通过经纬度对地球表面点位引用的坐标系。

2、大地坐标系是大地测量中以参考椭球面为基准面建立起来的坐标系。

二、作用不同

1、地理坐标系:定义了地表点位的经纬度,并且根据其所采用的参考椭球体参数还可求得点位的绝对高程值。

2、大地坐标系:是大地测量的基本坐标系,它是大地测量计算,地球形状大小研究和地图编制等的基础。

参考链接:地理坐标系和大地坐标系的区别_百度知道

代码分析:

#include <osgViewer/Viewer>
#include <osgEarth/Notify>
#include <osgEarthUtil/EarthManipulator>
#include <osgEarthUtil/ExampleResources>
#include <osgEarth/MapNode>
#include <osgEarth/ThreadingUtils>
#include <osgEarth/Metrics>
#include <osgEarth/Registry>
#include <iostream>

#include <osgEarthAnnotation/PlaceNode>

#include <osgEarthUtil/ClusterNode>

#define LC "[viewer] "

using namespace osgEarth;
using namespace osgEarth::Util;
using namespace osgEarth::Annotation;

int
usage(const char* name)
{
    OE_NOTICE
        << "\nUsage: " << name << " file.earth" << std::endl
        << MapNodeHelper().usage() << std::endl;

    return 0;
}

// 在extent的包围盒内,创建placeNode,1000=count,存储在nodes中
void makePlaces(MapNode* mapNode, unsigned int count, const GeoExtent& extent, osg::NodeList& nodes)
{    
    // set up a style to use for placemarks:
    Style placeStyle;    
	// 关闭清除
    placeStyle.getOrCreate<TextSymbol>()->declutter() = false;

    // A lat/long SRS for specifying points.获取地理坐标系
    const SpatialReference* geoSRS = mapNode->getMapSRS()->getGeographicSRS();

    //--------------------------------------------------------------------

    {
        osg::ref_ptr<osg::Image> pin = osgDB::readRefImageFile("icon.png");

        for (unsigned int i = 0; i < count; i++)
        {
			// 随机生成经纬度
            double lat = extent.yMin() + extent.height() * (rand() * 1.0) / (RAND_MAX - 1);
            double lon = extent.xMin() + extent.width() * (rand() * 1.0) / (RAND_MAX - 1);
            PlaceNode* place = new PlaceNode("Placemark", placeStyle, pin.get());
            place->setPosition(GeoPoint(geoSRS, lon, lat, 0.0));// 贴地
            place->setMapNode(mapNode);
            place->setDynamic(true);// 动态开启
            nodes.push_back(place);
        }
    }    
}

// 创建模型,传入的map节点、数量、在包围盒范围、模型node列表
void makeModels(MapNode* mapNode, unsigned int count, const GeoExtent& extent, osg::NodeList& nodes)
{
	// 读取模型,长宽高方向扩大的倍数???
    osg::ref_ptr< osg::Node > cessna = osgDB::readRefNodeFile("cessna.osg.10,10,10.scale");
    osg::ref_ptr< osg::Node > cow = osgDB::readRefNodeFile("cow.osg.100,100,100.scale");
    osgEarth::Registry::shaderGenerator().run(cessna.get());// 采用 默认着色器生成器
    osgEarth::Registry::shaderGenerator().run(cow.get());

    // A lat/long SRS for specifying points.地理坐标系
    const SpatialReference* geoSRS = mapNode->getMapSRS()->getGeographicSRS();

    bool useCow = false;

    for (unsigned int i = 0; i < count; i++)
    {
		// 随机生成经纬度
        double lat = extent.yMin() + extent.height() * (rand() * 1.0) / (RAND_MAX - 1);
        double lon = extent.xMin() + extent.width() * (rand() * 1.0) / (RAND_MAX - 1);

		// 接受地理空间坐标的变换节点 transform
        GeoTransform* transform = new GeoTransform();
		// 高度值默认1000
        transform->setPosition(GeoPoint(geoSRS, lon, lat, 1000));
        if (useCow)
        {
            transform->addChild(cow.get());
            transform->setName("cow");
        }        
        else
        {
            transform->addChild(cessna.get());
            transform->setName("plane");
        }
        nodes.push_back(transform);
        useCow = !useCow;// 两个模型交替绘制
    }
}

// 将view传入,创建控制面板容器
Container*
createControlPanel(osgViewer::View* view)
{
    ControlCanvas* canvas = ControlCanvas::getOrCreate(view);
    VBox* vbox = canvas->addControl(new VBox());// 垂直box
    vbox->setChildSpacing(10);
    return vbox;
}

// 设置半径控制事件,主要是改变clusterNode的包围盒半径
struct SetRadius : public ControlEventHandler
{
    SetRadius(ClusterNode* clusterNode) :
        _clusterNode( clusterNode )
    { }

    void onValueChanged(Control* control, float value)
    {
        _clusterNode->setRadius(value);
    }

    ClusterNode* _clusterNode;    
};

// 添加图标
struct AddIcons : public ControlEventHandler
{
    AddIcons(ClusterNode* clusterNode, MapNode* mapNode) :
        _clusterNode(clusterNode),
        _mapNode(mapNode)
    { }
	// 点击button时
    void onClick(Control* button)
    {
        osg::NodeList nodes;
        GeoExtent extent(SpatialReference::create("wgs84"), -180, -90, 180, 90);
        makePlaces(_mapNode, 1000, extent, nodes);// 在extent的包围盒内,创建placeNode
		std::cout << "PlaceNode nodes.size() " << nodes.size() << std::endl;
        for (unsigned int i = 0; i < nodes.size(); ++i)
        {
            _clusterNode->addNode(nodes[i].get());
        }
    }

    ClusterNode* _clusterNode;
    MapNode* _mapNode;
};

// 是否clusterNode使能
struct ToggleEnabled : public ControlEventHandler
{
    ToggleEnabled(ClusterNode* clusterNode) :
        _clusterNode(clusterNode)
    { }

    virtual void onValueChanged(Control* control, bool value) {
        _clusterNode->setEnabled(value);// 像碎玻璃,关闭后,所有文本、模型都变为点
    }

    ClusterNode* _clusterNode;
};

// 创建控件
void buildControls(Container* container, ClusterNode* clusterNode, MapNode* mapNode)
{
    // the outer container: 
	// new 网格容器
    Grid* grid = container->addControl(new Grid());
    grid->setBackColor(0, 0, 0, 0.5);// 半透明黑色背景
    grid->setMargin(10);// 外边距
    grid->setPadding(10);// 内边距
    grid->setChildSpacing(10);// 子控件间的距离
    grid->setChildVertAlign(Control::ALIGN_CENTER);// 子控件垂直居中
    grid->setAbsorbEvents(true);// 接收事件
    grid->setVertAlign(Control::ALIGN_TOP);// 网格控件

    // Radius 控制半径的标签
    LabelControl* radiusLabel = new LabelControl("Radius");
    radiusLabel->setVertAlign(Control::ALIGN_CENTER);
    grid->setControl(0, 0, radiusLabel);

	// 水平滑动条控件,进而改变clusterNode的包围盒半径
    HSliderControl* radiusAdjust = new HSliderControl(1, 500, clusterNode->getRadius(), new SetRadius(clusterNode));
    radiusAdjust->setWidth(125);
    radiusAdjust->setHeight(12);
    radiusAdjust->setVertAlign(Control::ALIGN_CENTER);
    grid->setControl(1, 0, radiusAdjust);
	// 将radiusAdiust的value值,写在lable控件上,用于显示半径
    grid->setControl(2, 0, new LabelControl(radiusAdjust));
    
	// 第1行第2列label控件
    grid->setControl(0, 1, new LabelControl("Enabled"));
    CheckBoxControl* checkBox = new CheckBoxControl(clusterNode->getEnabled());
    checkBox->setHorizAlign(Control::ALIGN_LEFT);
    checkBox->addEventHandler(new ToggleEnabled(clusterNode));// 切换是否clusterNode使能事件
    grid->setControl(1, 1, checkBox);// 第二行第二列

    // 1行3列,按钮,加图标事件,灰色的按钮,鼠标移动上去,会变为蓝色按钮
    grid->setControl(0, 2, new ButtonControl("Add Icons", new AddIcons(clusterNode, mapNode)));
    
}

//! Displays a simplified count for the cluster instead of the exact number.
// 简单计算个数的回调方法
class SimplifyCountCallback : public ClusterNode::StyleClusterCallback
{
public:
    virtual void operator()(ClusterNode::Cluster& cluster)
    {        
        if (cluster.nodes.size() >= 100)
        {
            cluster.marker->setText("100+");
        }
        else if (cluster.nodes.size() >= 50)
        {
            cluster.marker->setText("50+");
        }
        else if (cluster.nodes.size() >= 25)
        {
            cluster.marker->setText("25+");
        }
        else if (cluster.nodes.size() >= 10)
        {
            cluster.marker->setText("10+");
        }
        else
        {
            cluster.marker->setText("2+");
        } 
    }
};

//! Changes the name of a marker based on the name of the clustered nodes.
class StyleByNameCallback : public ClusterNode::StyleClusterCallback
{
public:

    StyleByNameCallback()
    {
        _planeImage = osgDB::readRefImageFile("airport.png");
        _cowImage = osgDB::readRefImageFile("hospital.png");
    }
    
    virtual void operator()(ClusterNode::Cluster& cluster)
    {    
        std::stringstream buf;
        buf << cluster.nodes[0]->getName() << "(" << cluster.nodes.size() << ")" << std::endl;
        cluster.marker->setText(buf.str());

        if (cluster.nodes[0]->getName() == "plane")
        {
            cluster.marker->setIconImage(_planeImage.get());
        }
        else if (cluster.nodes[0]->getName() == "cow")
        {
            cluster.marker->setIconImage(_cowImage.get());
        } 
    }

    osg::ref_ptr< osg::Image > _planeImage;
    osg::ref_ptr< osg::Image > _cowImage;
};

//! Only allows nodes with the same name to be clustered together.
class ClusterByNameCallback : public ClusterNode::CanClusterCallback
{
public:
    virtual bool operator()(osg::Node* a, osg::Node* b)
    {
        return (a->getName() == b->getName());
    }
};


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

    // help?
    if (arguments.read("--help"))
        return usage(argv[0]);

    // create a viewer:
    osgViewer::Viewer viewer(arguments);

    //Create the control panel 创建控制面板    
    Container* container = createControlPanel(&viewer);

    // Tell the database pager to not modify the unref settings
	// 通知数据库paper不要修改不相关设置
    viewer.getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, false);

    // install our default manipulator (do this before calling load)
	// 安装默认操作器,此时操作器的参数从arguments中读取
    viewer.setCameraManipulator(new EarthManipulator(arguments));

    // disable the small-feature culling
    viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);

    // set a near/far ratio that is smaller than the default. This allows us to get
    // closer to the ground without near clipping. If you need more, use --logdepth
	// 将 近远率小于默认值,不需要剪裁就能离地面更近,如果需要更近,则设置 --logdepth(在cmd中设置)
    viewer.getCamera()->setNearFarRatio(0.0001);

    // load an earth file, and support all or our example command-line options
    // and earth file <external> tags   
	// earth文件中,需要有 external 额外的标签,用来设置额外属性
    osg::Node* node = MapNodeHelper().load(arguments, &viewer);
    if (node)
    {
        MapNode* mapNode = MapNode::findMapNode(node);
        osg::NodeList nodes;

        //GeoExtent extent(SpatialReference::create("wgs84"), -180, -90, 180, 90);
		// 坐标系,西,南,东,北。构造GeoExtent.
		// 在给定的坐标系和坐标下,定义包围盒。
        GeoExtent extent(SpatialReference::create("wgs84"), -160.697021484375, 18.208480196039883, -153.951416015625, 22.978623970384913);
        
		// 创建模型,数量总共10000个,nodes.push_back(transform);transform中有10000个模型
        makeModels(mapNode, 10000, extent, nodes);

		// ClusterNode将重叠的节点聚集到屏幕上的PlaceNode中,以避免视觉混乱并提高性能。
        ClusterNode* clusterNode = new ClusterNode(mapNode, osgDB::readImageFile("../data/placemark32.png"));
        clusterNode->setStyleCallback(new StyleByNameCallback());// 设置名称回调
        clusterNode->setCanClusterCallback(new ClusterByNameCallback());// 设置是否允许回调操作
		std::cout << "nodes.size() = " << nodes.size() << std::endl;
        for (unsigned int i = 0; i < nodes.size(); i++)
        {
            clusterNode->addNode(nodes[i].get());
        }              
        mapNode->addChild(clusterNode);

		// 创建控制面板
        buildControls(container, clusterNode, mapNode);

        viewer.setSceneData(node);

        while (!viewer.done())
        {
            viewer.frame();
        }
        return viewer.run();
    }
    else
    {
        return usage(argv[0]);
    }

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/64306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]计算机毕业设计JAVA影院售票系统

[附源码]计算机毕业设计JAVA影院售票系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis M…

Mockito verify Junit5集成 Mockito

Mockito 集成 Junit5 在学习Mockito 如何集成 Junit5 之前&#xff0c;先来学习下 Mockito 基础的verify功能。 Maven依赖 本篇博客代码的Maven依赖如下&#xff0c;源码地址 <dependencies><dependency><groupId>org.springframework</groupId>&l…

分享107个小清新ppt模板,总有一款适合您

PPT下载链接&#xff1a;https://pan.baidu.com/s/1WqaR_29avEgq46iTSLKfmw?pwd5r81 提取码&#xff1a;5r81 源码下载链接&#xff1a;ppt.rar - 蓝奏云 采集的参数 page_count 1 # 每个栏目开始业务content"text/html; charsetgb2312"base_url "https:…

大一学生《Web编程基础》期末网页制作 基于HTML+CSS+JavaScript响应式个人主页相册介绍模板

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

UNIX环境高级编程_文件IO_文件描述表

这篇文件记录文件描述表。 2 文件描述符表 2.1 什么是文件描述符表 当运行一个程序时&#xff0c;内核会创建进程表task_struct。当open 打开文件后&#xff0c;会在进程表中创建相应的结构体来记录打开的文件&#xff0c;这个结构体就是文件描述符表。 2.2 task_struct 与…

Apple M1 开启HiDPI的新方法,无需虚拟屏,无需SwitchResX

之前折腾2K屏开HiDPI时记录过三种方法&#xff1a; 2020年03月&#xff1a;SwitchResX 开启HiDPI时显示Not installed的解决办法 2020年11月&#xff1a;升级macOS Big Sur 后 HIDPI失效的解决办法 2021年12月&#xff1a; Apple M1 开启HiDPI的新方法&#xff0c;无需关闭SIP&…

MySQL学习笔记(十三)count(*),count(id),count(1),count(字段)区别

count count 是MySQL的一个查询数量统计的函数&#xff0c;我们在平常的工作中经常会用到&#xff0c;count(*),count(id),count(1),count(字段)这4种写法有什么区别呢&#xff1f; //星号 select count(*) from user; //常数 select count(1) from user; //id(主键) select …

片内总线在cpu扮演什么角色?他为什么能实现高效,不同的CPU为什么采用不同的总线协议?

文章目录各种新型片上总线维度&#xff08;Degree&#xff09;跳&#xff08;Hop&#xff09;和跳数&#xff08;Hop Count&#xff0c;HC&#xff09;直连拓扑和路由器Intel的Ring和Mesh总线双Ring结构Mesh Bus结论片上总线&#xff0c;也称作片上网络&#xff08;Network on …

毕业设计-基于机器视觉的火灾烟雾检测识别系统-yolo

目录 前言 课题背景和意义 实现技术思路 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java基于网络安全维护的机房设备管理19rya

对于计算机专业的学生最头疼的就是临近毕业时的毕业设计,对于如何选题,技术选型等这些问题,难道了大部分人,确实,还没毕业的学生对于这些问题还比较陌生,只有学习的理论知识,没有实战经验怎么能独自完成毕业设计这一系列的流程,今天我们就聊聊如何快速应对这一难题. 比较容易的…

LSM Tree

LSM Tree 的存储模型&#xff0c;包括 Tidb&#xff0c;HBase等 特点 通过将大量的随机写转换为顺序写&#xff0c;从而极大地提升了数据写入的性能&#xff0c;虽然与此同时牺牲了部分读的性能。 只适合存储 key 值有序且写入大于读取的数据&#xff0c;或者读取操作通常是…

vueX持久化存储插件

场景&#xff1a;我们在做vue项目时&#xff0c;会遇到存储一些公共值&#xff0c;这样方便在不同的页面去调用这些值 vue中有个vuex&#xff0c;一般我们都存储在这里&#xff0c;这样在每个页面都能够调用 但是&#xff0c;当页面刷新了&#xff0c;这些值就被自动清理掉了 这…

第七届 Sky Hackathon 笔记集合贴

Alex_McAvoy--------NVIDIA 7th SkyHackathon&#xff08;二&#xff09;开发套件的安装与测试 Alex_McAvoy--------NVIDIA 7th SkyHackathon&#xff08;三&#xff09;语音数据集的制作 Alex_McAvoy--------NVIDIA 7th SkyHackathon&#xff08;四&#xff09;Nemo ASR 模型训…

vue.js ES6对象字面量的增强写法

1.属性的增强写法 ES5的写法&#xff1a; 运行效果 ES6的写法&#xff1a; 运行效果 2.函数的增强写法 ES5的写法&#xff1a; ES6的写法&#xff1a; 完整代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"…

基于Javamail的邮件收发系统(系统+论文+开题报告+任务书+外文翻译+文献综述+答辩PPT)

毕业设计&#xff08;论文&#xff09; &#xff08; 20 届&#xff09; 论文&#xff08;设计&#xff09;题目 基于Javamail的邮件收发系统 作 者 二级学院、专业 班 级 指导教师&#xff08;职称&#xff09; 论 文 字 数 论文完成时间 20年月日 基于JavaMail的邮件…

169-Rust和Solana环境配置

之前写过一篇Rust和Solana环境配置的 应该说写得是非常非常好 并不是说博文写得好 而是说写得非常非常的必要 比如我现在就是想要搞Rust和Solana配置 但是好久好久没搞了 已经完全忘记了 看到那一篇博文的时候就感觉想起来了很多 如果可以参考那篇完整做下来的话 就会…

是时候展示给大家这5款压箱底的软件了

是时候把自己压箱底的软件都发出来了&#xff0c;软件都是小巧耐用&#xff0c;不带广告的&#xff0c;赶紧下载起来吧&#xff01;就算暂时用不到的&#xff0c;也可以收藏起来等需要的时候再来下载&#xff01; 1.PPT演示——Focusky 一个高效的动画 PPT 演示软件&#xff0…

Linux学习-71-GRUB手动安装方法

16.13 GRUB手动安装方法 需要手工安装 GRUB 主要有两种情况&#xff1a; Linux 系统原先不是使用 GRUB 作为引导程序而现在想要使用 GRUB 来作为引导程序&#xff1b;MBR 中的引导程序被覆盖&#xff0c;需要在 MBR 中重新安装 GRUB&#xff0c;如先安装了 Linux 系统&#xf…

主成分分析的基本原理

目录 什么是主成分分析&#xff1f; 主成分分析的步骤 根据什么选择主成分&#xff1f; 怎样解释主成分&#xff1f; 特征值、方差解释率及碎石图 载荷系数与共同度 什么是主成分分析&#xff1f; 主成分的概念由Karl Pearson在1901年提出的。他是考察多个 变量间相关性…

Qt扫盲-Qt Creator IDE使用总结

Qt Creator IDE使用总结一、欢迎页1. 最近项目2. 示例3. 教程二、编辑页1. 左侧菜单区1. 工具栏2. 项目目录常用操作介绍1. 项目名2. 中间编码区1.顶部工具区2.编码区的常用功能1. 文件操作2. 类的一些方便操作1、Follow Symbol Under Cursor2. 查找和替换3. Refactor4. F1帮助…