osgEarth3.2+qt5.14+osg3.6.5
#ifndef POLYGONDRAWER_H
#define POLYGONDRAWER_H
#include <osgEarth/MapNode>
#include <osgEarth/Geometry>
#include <osgEarth/GeoMath>
#include <osgEarth/Style>
#include<osgEarth/Feature>
#include<osgEarth/FeatureNode>
#include <osgViewer/Viewer>
#include <osgGA/GUIEventHandler>
#include <vector>
#include<map>
class PolygonDrawer: public osgGA::GUIEventHandler
{
public:
PolygonDrawer(osg::ref_ptr<osgEarth::MapNode>);
//开始绘制多边形
void beginDraw(const osg::Vec3d& lla);
//绘制移动虚线
void moveDraw(const osg::Vec3d& lla);
//实现短暂虚线消失,结束绘制形状
void resetDraw();
//删除选中的多边形
void removeSelectPolygon(osg::ref_ptr<osg::Node> node);
//删除最后创建的多边形
void removePolygon();
//删除所以多边形
void removeAllPolygons();
//获取绘制状态
bool getIsDrawPoly();
//设置绘制状态
void setIsDrawPoly(bool isDrawPoly);
//设置多边形填充颜色
void setDrawPolyFillColor(osgEarth::Color fillColor);
//隐藏所有多边形
void hideAllPolygons();
//显示所有多边形
void showAllPolygons();
//按照三种颜色隐藏区域
void togglePolygonByColor(const osgEarth::Color& color);
//事件处理
bool handle(const osgGA::GUIEventAdapter &ea,osgGA::GUIActionAdapter &aa);
private:
bool _isDrawPoly; //开启绘制矩形
osgEarth::Color _fillColor; //多边形填充颜色
osg::NodePath nodePaths; // 用于存储节点路径列表,在进行射线求交(intersections)时作为过滤条件
osg::ref_ptr<osgEarth::MapNode>_mapNode;// 当前使用的地图节点(osgEarth::MapNode),用于坐标转换、添加绘制节点等操作
osgEarth::Style _polygonStyle;// 定义多边形的样式,包括填充颜色、边界线样式以及高程显示设置
osg::ref_ptr<osgEarth::FeatureNode> _polygonFeatureNode;// 存储绘制的多边形的 FeatureNode,FeatureNode 包含了多边形的几何数据及样式信息,用于显示多边形
osgEarth::Style _stippleLineStyle;// 定义虚线提示的样式,用于在动态更新过程中绘制虚线(例如鼠标移动时显示预览边界)
osg::ref_ptr<osgEarth::FeatureNode> _stippleFeatureNode;// 存储虚线提示的 FeatureNode,用于显示实时更新的虚线效果
std::vector<osg::Vec3d> _clickedPoints; // 存储点击的点
std::vector<osg::ref_ptr<osgEarth::FeatureNode>> _polygonNodes; // 保存所有绘制的矩形
std::multimap<osgEarth::Color, osg::ref_ptr<osgEarth::FeatureNode>> _colorPolygonMap;//保存颜色和多边形映射关系
};
#endif // POLYGONDRAWER_H
#include "PolygonDrawer.h"
#include<QDebug>
PolygonDrawer::PolygonDrawer(osg::ref_ptr<osgEarth::MapNode> mapNode)
:_mapNode(mapNode)
,_polygonFeatureNode(nullptr)
,_stippleFeatureNode(nullptr)
,_isDrawPoly(false)
,_fillColor(NULL)
{
//这个非常重要,保证和地图表面交点是最上层
nodePaths.push_back(mapNode);
//设置区域填充颜色,透明度
_polygonStyle.getOrCreate<osgEarth::PolygonSymbol>()->fill()->color()= osgEarth::Color(_fillColor, 0.45);
// 边界线样式:白色,宽度2像素
osgEarth::LineSymbol* ls = _polygonStyle.getOrCreate<osgEarth::LineSymbol>();
ls->stroke()->color() = osgEarth::Color::White;
ls->stroke()->width() = 2.0f;
ls->stroke()->widthUnits() = osgEarth::Units::PIXELS;
// 高程符号设置:贴地渲染、垂直偏移
osgEarth::AltitudeSymbol* alt = _polygonStyle.getOrCreate<osgEarth::AltitudeSymbol>();
alt->clamping() = osgEarth::AltitudeSymbol::CLAMP_ABSOLUTE;
alt->technique() = osgEarth::AltitudeSymbol::TECHNIQUE_GPU;
alt->verticalOffset() = 0.1;
// ---------------------- 配置虚线(stipple)样式 ----------------------
// 创建或获取 LineSymbol,用于绘制动态虚线提示(例如鼠标移动时预览边界)
// 设置虚线颜色为红色、宽度为2、细分(tessellation)为20
osgEarth::LineSymbol* stippleLS = _stippleLineStyle.getOrCreate<osgEarth::LineSymbol>();
stippleLS->stroke()->color() = osgEarth::Color::Red;
stippleLS->stroke()->width() = 2.0;
stippleLS->tessellation() = 20.0;
stippleLS->stroke()->stipple() = 255; // 定义虚线样式
// 配置虚线使用的高程符号,确保虚线也能贴地显示并有适当的垂直偏移
osgEarth::AltitudeSymbol* stippleAlt = _stippleLineStyle.getOrCreate<osgEarth::AltitudeSymbol>();
stippleAlt->clamping() = osgEarth::AltitudeSymbol::CLAMP_ABSOLUTE;
stippleAlt->technique() =osgEarth:: AltitudeSymbol::TECHNIQUE_GPU;
stippleAlt->verticalOffset() = 0.1;
}
void PolygonDrawer::beginDraw(const osg::Vec3d &lla)
{
//存储点击点
_clickedPoints.push_back(lla);
//如果小于两个点,不会绘制多边形
if (_clickedPoints.size() <= 2) {
return;
}
//如果 _featureNode 还不存在,就创建一个新的 Feature 对象
if (_polygonFeatureNode == nullptr) {
osgEarth::Feature* feature = new osgEarth::Feature(new osgEarth::Polygon,osgEarth::SpatialReference::get("wgs84"), _polygonStyle);
_polygonFeatureNode = new osgEarth::FeatureNode(feature,_polygonStyle);
_mapNode->addChild(_polygonFeatureNode);
// 记录颜色和多边形的对应关系
osgEarth::Color polyColor = _polygonStyle.getOrCreate<osgEarth::PolygonSymbol>()->fill()->color();
// 将相同颜色的多个多边形存入 multimap
_colorPolygonMap.insert(std::make_pair(polyColor, _polygonFeatureNode));
//添加绘制的多边形
_polygonNodes.push_back(_polygonFeatureNode);
}
if(_fillColor==osgEarth::Color::Red){
_polygonFeatureNode->setName("Polygon_Red");
}else{
_polygonFeatureNode->setName("Polygon");
}
// 更新多边形的几何数据:清空原有数据,加入所有采集的点
osgEarth::Geometry* geom = _polygonFeatureNode->getFeature()->getGeometry();
geom->clear();
for (auto& n : _clickedPoints) {
geom->push_back(n);
}
_polygonFeatureNode->dirty();
// 若存在虚线提示节点,则清空其数据
if (_stippleFeatureNode.valid()) {
_stippleFeatureNode->getFeature()->getGeometry()->clear();
}
}
void PolygonDrawer::moveDraw(const osg::Vec3d &lla)
{
// 若采集点不足,则无需更新虚线
if (_clickedPoints.size() < 2) {
return;
}
// 如果虚线节点尚未创建,则新建一个使用 LineString 几何的节点
if (!_stippleFeatureNode.valid()) {
osgEarth::Feature* feature = new osgEarth::Feature(new osgEarth::LineString, osgEarth::SpatialReference::get("wgs84"));
_stippleFeatureNode = new osgEarth::FeatureNode(feature,_stippleLineStyle);
_mapNode->addChild(_stippleFeatureNode.get());
}
// 更新虚线的几何数据,形成从第一个点 -> 当前鼠标点 -> 最后一个点的动态提示效果
osgEarth::Geometry* geom = _stippleFeatureNode->getFeature()->getGeometry();
geom->clear();
geom->push_back(_clickedPoints.front());
geom->push_back(lla);
geom->push_back(_clickedPoints.back());
_stippleFeatureNode->dirty();
}
void PolygonDrawer::resetDraw()
{
// 清空所有点击点
_clickedPoints.clear();
// 如果虚线节点存在,则从场景中移除,并将引用置空
if (_stippleFeatureNode.valid()) {
_mapNode->removeChild(_stippleFeatureNode.get());//使得绘制完一个形状后,红虚线消失
_stippleFeatureNode = nullptr;
}
_polygonFeatureNode = nullptr;
}
void PolygonDrawer::removeSelectPolygon(osg::ref_ptr<osg::Node> node)
{
_mapNode->removeChild(node); // 从地图中移除
}
void PolygonDrawer::removePolygon()
{
if (!_polygonNodes.empty()) {
osg::ref_ptr<osgEarth::FeatureNode> lastNode = _polygonNodes.back();
_mapNode->removeChild(lastNode); // 从地图中移除
_polygonNodes.pop_back(); // 从列表中移除
}
}
void PolygonDrawer::removeAllPolygons()
{
for (auto& node : _polygonNodes) {
_mapNode->removeChild(node); // 从地图中移除
}
_polygonNodes.clear(); // 清空列表
}
bool PolygonDrawer::getIsDrawPoly()
{
return _isDrawPoly;
}
void PolygonDrawer::setIsDrawPoly(bool isDrawPoly)
{
_isDrawPoly=isDrawPoly;
}
void PolygonDrawer::setDrawPolyFillColor(osgEarth::Color fillColor)
{
_fillColor=fillColor;
// 更新样式
_polygonStyle.getOrCreate<osgEarth::PolygonSymbol>()->fill()->color() = osgEarth::Color(_fillColor, 0.45);
}
void PolygonDrawer::hideAllPolygons()
{
for (auto& node : _polygonNodes)
{
node->setNodeMask(0); // 隐藏
}
}
void PolygonDrawer::showAllPolygons()
{
for (auto& node : _polygonNodes)
{
node->setNodeMask(0xffffffff); // 显示
}
}
void PolygonDrawer::togglePolygonByColor(const osgEarth::Color &color)
{
//使用 equal_range 获取所有匹配的多边形。
//不要直接比较 osgEarth::Color,而是转换成整数 RGB 形式进行匹配:osgEarth::Color 作为 std::multimap 的 key 可能无法精确匹配,因为颜色是浮点数,存在精度误差
int r = static_cast<int>(color.r() * 255);
int g = static_cast<int>(color.g() * 255);
int b = static_cast<int>(color.b() * 255);
for (auto it = _colorPolygonMap.begin(); it != _colorPolygonMap.end(); ++it)
{
osgEarth::Color storedColor = it->first;
int sr = static_cast<int>(storedColor.r() * 255);
int sg = static_cast<int>(storedColor.g() * 255);
int sb = static_cast<int>(storedColor.b() * 255);
if (sr == r && sg == g && sb == b) // 颜色匹配
{
osg::ref_ptr<osgEarth::FeatureNode> node = it->second;
node->setNodeMask(node->getNodeMask() == 0 ? 0xffffffff : 0);
}
}
}
bool PolygonDrawer::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa)
{
if (!_mapNode)
return false;
osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
if (!viewer)
return false;
// 处理 H 和 S 键,无论 _isDrawPoly 是否为 true
if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
{
switch (ea.getKey())
{
case osgGA::GUIEventAdapter::KEY_H: // 按下 'H' 键隐藏
qDebug() << "H key pressed - Hiding polygons";
hideAllPolygons();
return true;
case osgGA::GUIEventAdapter::KEY_S: // 按下 'S' 键显示
qDebug() << "S key pressed - Showing polygons";
showAllPolygons();
return true;
case osgGA::GUIEventAdapter::KEY_1:
qDebug() << "Toggling visibility for Blue polygon";
togglePolygonByColor(osgEarth::Color::Blue);
return true;
case osgGA::GUIEventAdapter::KEY_2:
qDebug() << "Toggling visibility for Yellow polygon";
togglePolygonByColor(osgEarth::Color::Yellow);
return true;
case osgGA::GUIEventAdapter::KEY_3:
qDebug() << "Toggling visibility for Red polygon";
togglePolygonByColor(osgEarth::Color::Red);
return true;
case osgGA::GUIEventAdapter::KEY_Delete: // 新增独立case
if (_isDrawPoly) { // 条件判断放在case内部
qDebug() << "Delete key pressed";
removePolygon();
return true;
}
break; // 不满足条件时继续传递事件
case osgGA::GUIEventAdapter::KEY_BackSpace:
if (_isDrawPoly) {
qDebug() << "BackSpace key pressed";
removeAllPolygons();
return true;
}
break;
default:
return true;
}
}
if (!_isDrawPoly)
return false;
// 使用 switch 结构更清晰地区分事件类型
switch (ea.getEventType())
{
case osgGA::GUIEventAdapter::PUSH:
{
switch (ea.getButton())
{
case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON:
{
osgUtil::LineSegmentIntersector::Intersections intersections;
if (viewer->computeIntersections(ea.getX(), ea.getY(), nodePaths, intersections))
{
const osgUtil::LineSegmentIntersector::Intersection& intersection = *intersections.begin();
osg::Vec3d worldPoint = intersection.getWorldIntersectPoint();
// 坐标转换:世界坐标 -> 地理坐标(WGS84)
const osgEarth::SpatialReference* mapSRS = _mapNode->getMapSRS();
const osgEarth::SpatialReference* geoSRS = osgEarth::SpatialReference::get("wgs84");
if (!geoSRS)
{
std::cerr << "[Error] geoSRS is null!" << std::endl;
return false;
}
osgEarth::GeoPoint geoPoint;
geoPoint.fromWorld(mapSRS, worldPoint);
osgEarth::GeoPoint wgs84Point;
geoPoint.transform(geoSRS, wgs84Point);
double lon = wgs84Point.x();
double lat = wgs84Point.y();
double height = wgs84Point.z();
height=45;//固定高度防止闪烁,渲染优先级不冲突
// 调用 beginDraw 添加点击点
beginDraw(osg::Vec3d(lon, lat, height));
}
break;
}
case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON:
{
resetDraw();
osg::ref_ptr<osg::Node> PolygonNode = NULL;
osgUtil::LineSegmentIntersector::Intersections intersections;
if (viewer->computeIntersections(ea.getX(), ea.getY(), nodePaths, intersections)) {
for (const auto &intersection : intersections) {
for (auto i : intersection.nodePath) {
if (i->getName().find("Polygon") != std::string::npos) {
PolygonNode = i;
break;
}else if(i->getName().find("Polygon_Red") != std::string::npos){
PolygonNode = i;
break;
}
}
if (PolygonNode)
break;
}
}
// 先删除 Polygon
if (PolygonNode)
{
removeSelectPolygon(PolygonNode);
}
break;
}
default:
break;
}
break;
}
case osgGA::GUIEventAdapter::MOVE:
{
osgUtil::LineSegmentIntersector::Intersections intersections;
if (viewer->computeIntersections(ea.getX(), ea.getY(), nodePaths, intersections))
{
const osgUtil::LineSegmentIntersector::Intersection& intersection = *intersections.begin();
osg::Vec3d worldPoint = intersection.getWorldIntersectPoint();
const osgEarth::SpatialReference* mapSRS = _mapNode->getMapSRS();
const osgEarth::SpatialReference* geoSRS = osgEarth::SpatialReference::get("wgs84");
osgEarth::GeoPoint geoPoint;
geoPoint.fromWorld(mapSRS, worldPoint);
osgEarth::GeoPoint wgs84Point;
geoPoint.transform(geoSRS, wgs84Point);
double lon = wgs84Point.x();
double lat = wgs84Point.y();
double height = wgs84Point.z();
// 调试输出,确认MOVE事件
// 鼠标移动时更新绘制状态(动态提示线)
height=45;//固定高度防止闪烁,渲染优先级不冲突
moveDraw(osg::Vec3d(lon, lat, height));
}
}
break;
default:
break;
}
return false;
}