开发环境:
win10 64bit、Qt5.15.2、C++ 、MSVC2019 、osg3.5.6、 osgEarth3.1
接触osgEarth不久,贴出来,希望大家指正。
注意osgEarth版本。
采用观察者设计模式,设置 master 和 slave 窗口,通过管理类和信号槽维护窗口观察者列表,实现信息高效传递。
达成多窗口在视点、视口、键鼠操作等方面的同步,确保操作连贯性和流畅性。
在 2D 和 3D 地图实现线、多边形、矩形同步绘制及 geometry 贴地显示,设置便捷绘制模式,提高绘图效率。
不同窗口可加载相同地理区域不同类型的文件。
观察者接口:
#ifndef OBSERVER_H
#define OBSERVER_H
#include <osg/Matrixd>
#include <osgEarth/EarthManipulator>
namespace MyProject {
// 观察者接口类,定义多个窗口需要同步的行为
class Observer {
public:
virtual ~Observer() = default;
// 同步更新视图矩阵
virtual void updateViewMatrix(const osg::Matrixd& viewMatrix) = 0;
// 同步更新投影矩阵
virtual void updateProjectionMatrix(const osg::Matrixd& projectionMatrix) = 0;
// 同步鼠标事件
virtual void updateMouseEvent(int x, int y) = 0;
// 同步键盘事件
virtual void updateKeyEvent(int key) = 0;
// 同步滚轮事件
virtual void updateViewpoint(const osgEarth::Viewpoint &vp) = 0;
// 同步2D 3D切换
virtual void updatePitch(const double &pitch) = 0;
// 同步 航向角
virtual void updateHeading(const double &heading) = 0;
// 新建 geometry
virtual void updateCreateGeometry() = 0;
// 同步 绘制图形
virtual void updateGeometry(osg::ref_ptr<osgEarth::Geometry> geometry) = 0;
};
}
#endif // OBSERVER_H
窗口应用:用到了osgQt 这个库, 大佬应该知道,osgQOpenGLWidget是继承自QOpenGLWidget。
#include "MyWidget.h"
#include "PickHandler.h"
#include "qdebug.h"
#include "qicon.h"
#include <osgDB/ReadFile>
#include <osgEarth/MapNode>
#include <osgEarth/ImageOverlay>
#include <osgEarth/AnnotationLayer>
#include <osgEarth/Registry>
//! Callback that you install on the RTTPicker.
struct MyCallback : public RTTPicker::Callback
{
MyCallback(){ }
void onHit(ObjectID id)
{
qDebugV0()<<id;
}
void onMiss()
{
qDebugV0()<<"Miss";
}
// pick whenever the mouse moves.
bool accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa)
{
// qDebugV0()<<"accept";
return ea.getEventType() == ea.PUSH;
}
};
MyWidget::MyWidget( QWidget* parent ) : osgQOpenGLWidget( parent ) {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setParent(nullptr);
setWindowIcon(QIcon(""));
setFocusPolicy(Qt::StrongFocus);
setFocus();
setMouseTracking(true);
osgEarth::initialize();
Global::initStyle(_style);
_map = new Map();
_mapNode = new osgEarth::MapNode( _map );
_map->addLayer( new GeodeticGraticule() );//经纬度网格
_group = new osg::Group();
_mapNode->addChild(_group);
_controlCanvas = new Controls::ControlCanvas;
_group->addChild( _controlCanvas );
// coordinate label
_labelReadout = new LabelControl();
_labelReadout->setHorizAlign(Control::ALIGN_LEFT);
_labelReadout->setVertAlign(Control::ALIGN_BOTTOM);
_labelReadout->setForeColor( 255, 0, 0, 1 );
_controlCanvas->addControl( _labelReadout.get() );
// _mouseCoordsTool = new osgEarth::Contrib::MouseCoordsTool( _mapNode );
// _mouseCoordsTool->addCallback(new osgEarth::Contrib::MouseCoordsLabelCallback(_labelReadout));
poLayerImage = Global::loadImage("C:/Users/xuanm/Pictures/shp/mid.tif");
_map->addLayer( poLayerImage );
// poLayerElevation = Global::loadElevation("C:/Users/xuanm/Pictures/vector_demo/n44_w092_1arc_v3.tif");
// _map->addLayer( poLayerElevation );
poLayerVector = Global::loadShapefile("C:/Users/xuanm/Pictures/shp/mid.shp");
_map->addLayer( poLayerVector );
connect(this, &osgQOpenGLWidget::initialized, this, &MyWidget::updateWidget);
}
MyWidget::~MyWidget() {
}
void MyWidget::updateWidget() {
osgEarth::initialize( );
_viewer = getOsgViewer();
_viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);//设置单线程模式
_viewer->realize();
_viewer->addEventHandler( new PickHandler(new osgText::Text) );
_viewer->setSceneData( _mapNode );
updateViewpoint( poLayerVector );
_viewer->requestRedraw();
}
void MyWidget::slotType(int type)
{
this->type = type;
if(eventHandler){
eventHandler->updateType(type);
}
featureNode = nullptr;
}
void MyWidget::enterEvent(QEvent *event) {
setFocus();
emit sigEnter(true);
// //widget 由没有获得焦点--->获得焦点
if(!isEnter){
_earthManipulator->setHomeViewpoint(currentViewpoint );
}
isEnter = true;
//至关重要
_viewer->setCameraManipulator(_earthManipulator);
}
void MyWidget::leaveEvent(QEvent *event) {
clearFocus();
_labelReadout->setText("");
emit sigEnter(false);
isEnter = false;
}
void MyWidget::updateViewpoint(Layer * poLayer) {
// 设置相机的初始视图,以聚焦在加载的图像区域上 // 获取 Shapefile 的地理范围
GeoExtent extent = poLayer->getExtent();
if (extent.isValid())
{
// 获取地理范围的最小和最大经纬度
double minLon = extent.xMin();
double maxLon = extent.xMax();
double minLat = extent.yMin();
double maxLat = extent.yMax();
// 计算中心点
double centerLon = (minLon + maxLon) / 2.0;
double centerLat = (minLat + maxLat) / 2.0;
// 创建 GeoPoint 作为中心点
GeoPoint center(extent.getSRS(), centerLon, centerLat, 0.0);
// 创建 GeoPoint 表示四个角的经纬度
GeoPoint bottomLeft(extent.getSRS(), minLon, minLat, 0.0);
GeoPoint topRight(extent.getSRS(), maxLon, maxLat, 0.0);
// 计算宽度和高度(物理距离,单位:米)
double width = bottomLeft.distanceTo(GeoPoint(extent.getSRS(), maxLon, minLat));
double height = bottomLeft.distanceTo(GeoPoint(extent.getSRS(), minLon, maxLat));
// 使用勾股定理计算影像对角线的长度
double diagonal = std::sqrt(width * width + height * height);
//qDebugV0() << "实际的对角线长度 (米) " << diagonal;
// 根据经验公式(可以根据实际效果调整系数)计算Range
double range = diagonal / ( 2.0 * std::tan(osg::DegreesToRadians(45.0)) );
if(_viewpoint == nullptr) {
//设置 Viewpoint
_viewpoint = new Viewpoint();
_viewpoint->setName( "Shapefile Viewpoint" );
_viewpoint->setFocalPoint( center );
_viewpoint->setHeading(Angle(0.0, Units::DEGREES));
_viewpoint->setPitch(Angle(-90.0, Units::DEGREES));
_viewpoint->setRange(Distance(range, Units::METERS));
// 创建 EarthManipulator 并设置 Viewpoint
_earthManipulator = new osgEarth::Util::EarthManipulator();
_earthManipulator->setHomeViewpoint(*_viewpoint);
_earthManipulator->home(0.0);
// 设置 Viewer 的 Camera Manipulator
_viewer->setCameraManipulator( _earthManipulator.get() );
}
if(eventHandler == nullptr){
eventHandler = new CustomEventHandler();
_viewer->addEventHandler( eventHandler );
}
_viewpoint->setFocalPoint( center );
_viewpoint->setRange(Distance(range, Units::METERS));
currentViewpoint = *_viewpoint;
_earthManipulator->setViewpoint(*_viewpoint);
}
}
void MyWidget::updateCreateGeometry()
{
qDebugV0()<<"type: "<<type;
feature = new osgEarth::Feature(new osgEarth::Geometry, _map->getSRS());
feature->geoInterp() = GEOINTERP_RHUMB_LINE;
featureNode = new FeatureNode(feature, _style);
_mapNode->addChild(featureNode);
}
void MyWidget::updateGeometry(osg::ref_ptr<osgEarth::Geometry> geometry)
{
feature->setGeometry(geometry);
featureNode->dirty();
_viewer->requestRedraw(); // 请求视图重绘 // 触发重新绘制
}
// 观察者接口实现 - 更新视图矩阵
void MyWidget::updateViewMatrix(const osg::Matrixd& viewMatrix) {
_viewer->setCameraManipulator(nullptr);
_viewer->getCamera()->setViewMatrix(viewMatrix);
_viewer->requestRedraw();
}
// 观察者接口实现 - 更新投影矩阵
void MyWidget::updateProjectionMatrix(const osg::Matrixd& projectionMatrix) {
_viewer->setCameraManipulator(nullptr);
_viewer->getCamera()->setProjectionMatrix( projectionMatrix );
_viewer->requestRedraw();
}
// 观察者接口实现 - 同步鼠标事件
void MyWidget::updateMouseEvent(int x, int y) {
//qDebugV0() << "Mouse Event received at: (" << x << ", " << y << ")";
}
// 观察者接口实现 - 同步键盘事件
void MyWidget::updateKeyEvent(int key) {
//qDebugV0() << "Key Event received: " << key;
}
//更新视点
void MyWidget::updateViewpoint(const osgEarth::Viewpoint& viewpoint) {
currentViewpoint = viewpoint;
// _earthManipulator->setViewpoint(viewpoint );
// _viewer->setCameraManipulator(_earthManipulator);
}
void MyWidget::updatePitch(const double &pitch)
{
_earthManipulator->setHomeViewpoint(currentViewpoint );
_viewer->setCameraManipulator(_earthManipulator);
_viewpoint->setPitch(Angle(pitch, Units::DEGREES));
_earthManipulator->setViewpoint(*_viewpoint);
_viewer->requestRedraw();
}
void MyWidget::updateHeading(const double &heading)
{
_earthManipulator->setHomeViewpoint(currentViewpoint );
_viewer->setCameraManipulator(_earthManipulator);
_viewpoint->setHeading(Angle(heading, Units::DEGREES));
_earthManipulator->setViewpoint(*_viewpoint);
_viewer->requestRedraw();
}
void MyWidget::resizeEvent(QResizeEvent* event) {
// Call base class implementation
osgQOpenGLWidget::resizeEvent(event);
//qDebug() << "MyWidget resized to:" << event->size();
// 如果需要,处理OpenGL视口
glViewport(0, 0, event->size().width(), event->size().height());
}
自定义osgGA::GUIEventHandler,实现绘制交互。这个逻辑写得乱,希望大佬们抽空给指正哈。
#include "CustomEventHandler.h"
#include "Global.h"
#include "MyWidget.h"
CustomEventHandler::CustomEventHandler() {
Global::initStyle(_style);
observers.clear();
}
void CustomEventHandler::addObserver(MyWidget* observer) {
observers.insert(observer);
}
void CustomEventHandler::removeObserver(MyWidget* observer) {
observers.erase(observer);
}
void CustomEventHandler::updateType(const int& type)
{
this->type = type;
_geometry = nullptr;
}
bool CustomEventHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) {
//将aa强制转换为viewer对象
poViewer = dynamic_cast<osgViewer::Viewer*>(&aa);
if (poViewer == nullptr){
return false;
}
poEarthManipulator = dynamic_cast<osgEarth::Util::EarthManipulator *> (poViewer->getCameraManipulator());
if(poEarthManipulator == nullptr){
return false;
}
viewpoint = poEarthManipulator->getViewpoint();
//获取 MaNode;
poMapNode = dynamic_cast<osgEarth::MapNode*>(poViewer->getSceneData());
if(poMapNode == nullptr){
return false;
}
poMap = poMapNode->getMap();
if(poMap == nullptr){
return false;
}
mx = ea.getX();
my = ea.getY();
eEventType = ea.getEventType();
eModKeyMask = ea.getModKeyMask();
eKey = ea.getKey();
eButton = ea.getButton();
this->getModKeyMask();//shift ctrl
// 同步视图和投影矩阵
osg::Matrixd viewMatrix = poViewer->getCamera()->getViewMatrix();
osg::Matrixd projMatrix = poViewer->getCamera()->getProjectionMatrix();
notifyViewMatrixChanged(viewMatrix);
notifyProjectionMatrixChanged(projMatrix);
notifyViewpoint(viewpoint);
if(type == 0 ){
return this->handleNone();
}else{
return this->handleDraw();
}
}
bool CustomEventHandler::handleNone()
{
switch (eEventType) {
case osgGA::GUIEventAdapter::KEYDOWN: // 键盘按下事件
{
//2D 3D 切换;viewPoint heading 航向角切换
if(_ctrlDown) {
switch (eKey) {
case osgGA::GUIEventAdapter::KEY_2: {
viewpoint.setPitch(Angle(-90.0, Units::DEGREES));
break;
}
case osgGA::GUIEventAdapter::KEY_3: {
viewpoint.setPitch(Angle(-15.0, Units::DEGREES));
break;
}
case osgGA::GUIEventAdapter::KEY_Up: {
viewpoint.setHeading(Angle(0.0, Units::DEGREES));
break;
}
case osgGA::GUIEventAdapter::KEY_Left: {
viewpoint.setHeading(Angle(90.0, Units::DEGREES));
break;
}
case osgGA::GUIEventAdapter::KEY_Down: {
viewpoint.setHeading(Angle(180.0, Units::DEGREES));
break;
}
case osgGA::GUIEventAdapter::KEY_Right: {
viewpoint.setHeading(Angle(270.0, Units::DEGREES));
break;
}
default:
break;
}
poEarthManipulator->setViewpoint(viewpoint);
}
break;
}
default:
break;
}
return false;
}
// 按住 shift键, 绘制 点 线 面
bool CustomEventHandler::handleDraw()
{
if(!_shiftDown){
return false;
}
switch (eEventType) {
case osgGA::GUIEventAdapter::PUSH:
{
if(eButton != osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON){
return false;
}
if(_geometry == nullptr){
switch(type){
case GEOMETRY_LINE:
// qDebugV0()<<"line";
_geometry = new osgEarth::LineString;
break;
case GEOMETRY_RECT:
case GEOMETRY_POLYGON:
qDebugV0()<<"rect or polygon ";
_geometry = new osgEarth::Polygon;
break;
case GEOMETRY_FENCE:
break;
}
_feature = new osgEarth::Feature(_geometry, poMap->getSRS());
_feature->geoInterp() = GEOINTERP_RHUMB_LINE;
_featureNode = new FeatureNode(_feature, _style);
poMapNode->addChild(_featureNode);
notifyCreateGeometry();
}
osg::Vec3d worldPos;
if (poMapNode->getTerrain()->getWorldCoordsUnderMouse(poViewer, mx, my, worldPos))
{
GeoPoint _GeoPoint;
_GeoPoint.fromWorld(poMapNode->getMapSRS(), worldPos);
osg::Vec3d mapPos;
mapPos = osg::Vec3d(_GeoPoint.x(), _GeoPoint.y(), _GeoPoint.z());
mapPosFirst = mapPos;
_geometry->push_back(mapPos);
_featureNode->dirty();
notifyGeometry(_geometry);
}
break;
}
case osgGA::GUIEventAdapter::RELEASE:
{
if(eButton != osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON){
return false;
}
// 绘制矩形的时候,鼠标左键一旦松开,则表示绘制结束
if (type == GEOMETRY_RECT) {
_geometry = nullptr;
}
break;
}
case osgGA::GUIEventAdapter::DRAG:
{
if (type == GEOMETRY_RECT) {
osg::Vec3d worldPos;
if (poMapNode->getTerrain()->getWorldCoordsUnderMouse(poViewer, mx, my, worldPos))
{
GeoPoint _GeoPoint;
_GeoPoint.fromWorld(poMapNode->getMapSRS(), worldPos);
osg::Vec3d mapPos;
mapPos = osg::Vec3d(_GeoPoint.x(), _GeoPoint.y(), _GeoPoint.z());
_geometry->clear();
_geometry->push_back(mapPosFirst);
_geometry->push_back(osg::Vec3d(mapPos.x(), mapPosFirst.y(), _GeoPoint.z()));
_geometry->push_back(mapPos);
_geometry->push_back(osg::Vec3d(mapPosFirst.x(), mapPos.y(), _GeoPoint.z()));
_featureNode->dirty();
notifyGeometry(_geometry);
}
}
break;
}
default:
break;
}
return false;
}
// shift ctrl 键 状态 检测
void CustomEventHandler::getModKeyMask()
{
//键盘 有键按下
if (eEventType == osgGA::GUIEventAdapter::KEYDOWN) {
if (eKey == osgGA::GUIEventAdapter::KEY_Shift_L || eKey == osgGA::GUIEventAdapter::KEY_Shift_R) {
_shiftDown = true;
}
if (eKey == osgGA::GUIEventAdapter::KEY_Control_L || eKey == osgGA::GUIEventAdapter::KEY_Control_R) {
_ctrlDown = true;
}
}
//键盘 有键抬起
if(eEventType == osgGA::GUIEventAdapter::KEYUP){
if (eKey == osgGA::GUIEventAdapter::KEY_Shift_L || eKey == osgGA::GUIEventAdapter::KEY_Shift_R) {
_shiftDown = false;
_geometry = nullptr;
}
if (eKey == osgGA::GUIEventAdapter::KEY_Control_L || eKey == osgGA::GUIEventAdapter::KEY_Control_R) {
_ctrlDown = false;
}
}
}
void CustomEventHandler::notifyViewMatrixChanged(const osg::Matrixd& viewMatrix) {
for (auto observer : observers) {
if(observer) {
observer->updateViewMatrix(viewMatrix);
}
}
}
void CustomEventHandler::notifyProjectionMatrixChanged(const osg::Matrixd& projectionMatrix) {
for (auto observer : observers) {
if(observer) {
observer->updateProjectionMatrix(projectionMatrix);
}
}
}
void CustomEventHandler::notifyMouseEvent(int x, int y) {
for (auto observer : observers) {
observer->updateMouseEvent(x, y);
}
}
void CustomEventHandler::notifyKeyEvent(int key) {
for (auto observer : observers) {
observer->updateKeyEvent(key);
}
}
void CustomEventHandler::notifyViewpoint(const osgEarth::Viewpoint &vp)
{
for (auto observer : observers) {
observer->updateViewpoint(vp);
}
}
void CustomEventHandler::notifyPitch(const double &pitch)
{
for (auto observer : observers) {
observer->updatePitch(pitch);
}
}
void CustomEventHandler::notifyHeading(const double &heading)
{
for (auto observer : observers) {
observer->updateHeading(heading);
}
}
void CustomEventHandler::notifyCreateGeometry()
{
for (auto observer : observers) {
observer->updateCreateGeometry();
}
}
void CustomEventHandler::notifyGeometry(osg::ref_ptr<osgEarth::Geometry> geometry)
{
for (auto observer : observers) {
observer->updateGeometry(geometry);
}
}
在一个管理类里面维护观察者列表:
// 实时 维护 观察者 列表
void MainWindow::slotEnterStatus(bool isEnter) {
// sender() 返回一个 QObject 指针,指向发出信号的对象
QObject* obj = sender();
MyWidget* poWidget = qobject_cast<MyWidget*>(obj);
if(!poWidget) {
return;
}
// qDebugV0()<<poWidget->windowTitle()<<" Focus-->"<<(isFocusIn?"IN":"OUT");
for(auto observer : listWidget) {
if(observer == poWidget) {
continue;
}
if(isEnter) {
poWidget->eventHandler->addObserver(observer);
}
else {
poWidget->eventHandler->removeObserver(observer);
}
}
}
/**********************************************************************************/
编辑*.shp文件,实现增删改。看了好多帖子,还是没搞定。有大佬知道的话留言。谢谢。