QGIS+qt 二次开发 缩放要素

本文详细介绍了如何在QGIS+qt环境下进行地图要素的缩放功能二次开发,从源码分析到代码调整,再到功能调用,最后展示了缩放效果。

目录

1 前言

2 缩放要素

3 代码调整

4 如何调用

5 最后效果


1 前言

        上一篇文章《QGIS+qt 二次开发 移动要素》中提到了如何实现要素移动的功能,本文来说一下要素的缩放功能怎么实现。

2 缩放要素

        打开QGIS,先导入一张shp图层文件:

 观察到要素工具栏里有缩放要素按钮,按下激活要素缩放功能。

 然后和移动要素一样,鼠标点击图层中的要素,再移动鼠标,会有缩放调整效果的展示,如下:

 

         有了之前移动要素的实现经验,我们还是先来看一下源码,在qgsmaptoolmovefeature.cpp的目录下,很快就能看见一个叫qgsmaptoolscalefeature.cpp的文件,打开看一下。

qgsmaptoolscalefeature.cpp

/***************************************************************************
    qgsmaptoolscalefeature.cpp  -  map tool for scaling features by mouse drag
    ---------------------
    Date                 : December 2020
    Copyright            : (C) 2020 by roya0045
    Contact              : ping me on github
 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <QSettings>
#include <QEvent>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>

#include <limits>
#include <cmath>

#include "qgsadvanceddigitizingdockwidget.h"
#include "qgsmaptoolscalefeature.h"
#include "qgsfeatureiterator.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsrubberband.h"
#include "qgsvectorlayer.h"
#include "qgstolerance.h"
#include "qgisapp.h"
#include "qgsspinbox.h"
#include "qgsdoublespinbox.h"
#include "qgssnapindicator.h"
#include "qgsmapmouseevent.h"


QgsScaleMagnetWidget::QgsScaleMagnetWidget( const QString &label, QWidget *parent )
  : QWidget( parent )
{
  mLayout = new QHBoxLayout( this );
  mLayout->setContentsMargins( 0, 0, 0, 0 );
  //mLayout->setAlignment( Qt::AlignLeft );
  setLayout( mLayout );

  if ( !label.isEmpty() )
  {
    QLabel *lbl = new QLabel( label, this );
    lbl->setAlignment( Qt::AlignRight | Qt::AlignCenter );
    mLayout->addWidget( lbl );
  }

  mScaleSpinBox = new QgsDoubleSpinBox( this );
  mScaleSpinBox->setSingleStep( 0.5 );
  mScaleSpinBox->setMinimum( 0 );
  mScaleSpinBox->setValue( 1 );
  mScaleSpinBox->setShowClearButton( false );
  mScaleSpinBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
  mLayout->addWidget( mScaleSpinBox );

  // connect signals
  mScaleSpinBox->installEventFilter( this );
  connect( mScaleSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsScaleMagnetWidget::scaleSpinBoxValueChanged );

  // config focus
  setFocusProxy( mScaleSpinBox );
}

void QgsScaleMagnetWidget::setScale( double scale )
{
  mScaleSpinBox->setValue( scale );
}

double QgsScaleMagnetWidget::scale() const
{
  return mScaleSpinBox->value();
}

bool QgsScaleMagnetWidget::eventFilter( QObject *obj, QEvent *ev )
{
  if ( obj == mScaleSpinBox && ev->type() == QEvent::KeyPress )
  {
    QKeyEvent *event = static_cast<QKeyEvent *>( ev );
    if ( event->key() == Qt::Key_Escape )
    {
      emit scaleEditingCanceled();
      return true;
    }
    if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return )
    {
      emit scaleEditingFinished( scale() );
      return true;
    }
  }

  return false;
}

void QgsScaleMagnetWidget::scaleSpinBoxValueChanged( double scale )
{
  emit scaleChanged( scale );
}

//
// QgsMapToolScaleFeature
//

QgsMapToolScaleFeature::QgsMapToolScaleFeature( QgsMapCanvas *canvas )
  : QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
  , mSnapIndicator( std::make_unique< QgsSnapIndicator>( canvas ) )
{
  mToolName = tr( "Scale feature" );
}

QgsMapToolScaleFeature::~QgsMapToolScaleFeature()
{
  deleteScalingWidget();
  mAnchorPoint.reset();
  deleteRubberband();
  mSnapIndicator->setMatch( QgsPointLocator::Match() );
}

void QgsMapToolScaleFeature::cadCanvasMoveEvent( QgsMapMouseEvent *e )
{
  mSnapIndicator->setMatch( e->mapPointMatch() );
  if ( mBaseDistance == 0 )
  {
    return;
  }
  if ( mScalingActive )
  {
    const double distance = mFeatureCenterMapCoords.distance( e->mapPoint() );
    const double scale = distance / mBaseDistance; // min 0 or no limit?

    if ( mScalingWidget )
    {
      disconnect( mScalingWidget, &QgsScaleMagnetWidget::scaleChanged, this, &QgsMapToolScaleFeature::updateRubberband );
      mScalingWidget->setScale( scale );
      mScalingWidget->setFocus( Qt::TabFocusReason );
      mScalingWidget->editor()->selectAll();
      connect( mScalingWidget, &QgsScaleMagnetWidget::scaleChanged, this, &QgsMapToolScaleFeature::updateRubberband );
    }
    updateRubberband( scale );
  }
}

void QgsMapToolScaleFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
{
  if ( !mCanvas )
  {
    return;
  }

  QgsVectorLayer *vlayer = currentVectorLayer();
  if ( !vlayer )
  {
    deleteScalingWidget();
    deleteRubberband();
    notifyNotVectorLayer();
    mSnapIndicator->setMatch( QgsPointLocator::Match() );
    mCadDockWidget->clear();
    return;
  }

  if ( e->button() == Qt::RightButton )
  {
    cancel();
    return;
  }

  // place anchor point on CTRL + click
  if ( e->modifiers() & Qt::ControlModifier )
  {
    if ( !mAnchorPoint )
    {
      mAnchorPoint = std::make_unique<QgsVertexMarker>( mCanvas );
      mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
    }
    mAnchorPoint->setCenter( e->mapPoint() );
    mFeatureCenterMapCoords = e->mapPoint();
    cadDockWidget()->clear();
    return;
  }

  deleteScalingWidget();

  // Initialize scaling if not yet active
  if ( !mScalingActive )
  {
    mScaling = 1;

    deleteRubberband();

    if ( !vlayer->isEditable() )
    {
      notifyNotEditableLayer();
      return;
    }

    const QgsPointXY layerCoords = toLayerCoordinates( vlayer, e->mapPoint() );
    const double searchRadius = QgsTolerance::vertexSearchRadius( mCanvas->currentLayer(), mCanvas->mapSettings() );
    const QgsRectangle selectRect( layerCoords.x() - searchRadius, layerCoords.y() - searchRadius,
                                   layerCoords.x() + searchRadius, layerCoords.y() + searchRadius );

    mAutoSetAnchorPoint = false;
    if ( !mAnchorPoint )
    {
      mAnchorPoint = std::make_unique<QgsVertexMarker>( mCanvas );
      mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
      mAutoSetAnchorPoint = true;
    }

    if ( vlayer->selectedFeatureCount() == 0 )
    {
      QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setNoAttributes().setFilterRect( selectRect ) );

      //find the closest feature
      const QgsGeometry pointGeometry = QgsGeometry::fromPointXY( layerCoords );
      if ( pointGeometry.isNull() )
      {
        return;
      }

      double minDistance = std::numeric_limits<double>::max();

      QgsFeature cf;
      QgsFeature f;
      while ( fit.nextFeature( f ) )
      {
        if ( f.hasGeometry() )
        {
          const double currentDistance = pointGeometry.distance( f.geometry() );
          if ( currentDistance < minDistance )
          {
            minDistance = currentDistance;
            cf = f;
          }
        }
      }

      if ( minDistance == std::numeric_limits<double>::max() )
      {
        emit messageEmitted( tr( "Could not find a nearby feature in the current layer." ) );
        if ( mAutoSetAnchorPoint )
          mAnchorPoint.reset();
        return;
      }

      mExtent = cf.geometry().boundingBox();
      if ( mAutoSetAnchorPoint )
      {
        mFeatureCenterMapCoords = toMapCoordinates( vlayer, mExtent.center() );
        mAnchorPoint->setCenter( mFeatureCenterMapCoords );
      }
      else
      {
        mFeatureCenterMapCoords =  mAnchorPoint->center();
      }

      mScaledFeatures.clear();
      mScaledFeatures << cf.id(); //todo: take the closest feature, not the first one...
      mOriginalGeometries << cf.geometry();

      mRubberBand = createRubberBand( vlayer->geometryType() );
      mRubberBand->setToGeometry( cf.geometry(), vlayer );
    }
    else
    {
      mScaledFeatures = vlayer->selectedFeatureIds();

      mRubberBand = createRubberBand( vlayer->geometryType() );

      QgsFeature feat;
      QgsFeatureIterator it = vlayer->getSelectedFeatures();
      while ( it.nextFeature( feat ) )
      {
        mRubberBand->addGeometry( feat.geometry(), vlayer, false );
        mOriginalGeometries << feat.geometry();
      }
      mRubberBand->updatePosition();
      mRubberBand->update();
    }

    mScalingActive = true;

    mBaseDistance = e->mapPoint().distance( mFeatureCenterMapCoords );
    mScaling = 1.0;

    createScalingWidget();

    mScalingActive = true;

    return;
  }

  applyScaling( mScaling );
}

void QgsMapToolScaleFeature::cancel()
{
  deleteScalingWidget();
  deleteRubberband();
  QgsVectorLayer *vlayer = currentVectorLayer();
  if ( vlayer->selectedFeatureCount() == 0 || mAutoSetAnchorPoint )
  {
    mAnchorPoint.reset();
  }
  mScalingActive = false;
  mSnapIndicator->setMatch( QgsPointLocator::Match() );
  mCadDockWidget->clear();
}

void QgsMapToolScaleFeature::updateRubberband( double scale )
{
  if ( mScalingActive && mRubberBand )
  {
    mScaling = scale;

    QgsVectorLayer *vlayer = currentVectorLayer();
    if ( !vlayer )
      return;

    const QgsPointXY layerCoords = toLayerCoordinates( vlayer, mFeatureCenterMapCoords );
    QTransform t;
    t.translate( layerCoords.x(), layerCoords.y() );
    t.scale( mScaling, mScaling );
    t.translate( -layerCoords.x(), -layerCoords.y() );

    mRubberBand->reset( vlayer->geometryType() );
    for ( const QgsGeometry &originalGeometry : mOriginalGeometries )
    {
      QgsGeometry geom = originalGeometry;
      geom.transform( t );
      mRubberBand->addGeometry( geom, vlayer );
    }
  }
}

void QgsMapToolScaleFeature::applyScaling( double scale )
{
  mScaling = scale;
  mScalingActive = false;

  QgsVectorLayer *vlayer = currentVectorLayer();
  if ( !vlayer )
  {
    deleteRubberband();
    notifyNotVectorLayer();
    mSnapIndicator->setMatch( QgsPointLocator::Match() );
    mCadDockWidget->clear();
    return;
  }

  //calculations for affine transformation

  vlayer->beginEditCommand( tr( "Features Scaled" ) );

  const QgsPointXY layerCoords = toLayerCoordinates( vlayer, mFeatureCenterMapCoords );
  QTransform t;
  t.translate( layerCoords.x(), layerCoords.y() );
  t.scale( mScaling, mScaling );
  t.translate( -layerCoords.x(), -layerCoords.y() );

  QgsFeatureRequest request;
  request.setFilterFids( mScaledFeatures ).setNoAttributes();
  QgsFeatureIterator fi = vlayer->getFeatures( request );
  QgsFeature feat;
  while ( fi.nextFeature( feat ) )
  {
    if ( !feat.hasGeometry() )
      continue;

    QgsGeometry geom = feat.geometry();
    if ( !( geom.transform( t ) == Qgis::GeometryOperationResult::Success ) )
      continue;

    const QgsFeatureId id = feat.id();
    vlayer->changeGeometry( id, geom );
  }

  deleteScalingWidget();
  deleteRubberband();
  mSnapIndicator->setMatch( QgsPointLocator::Match() );
  mCadDockWidget->clear();

  if ( mAutoSetAnchorPoint )
    mAnchorPoint.reset();

  vlayer->endEditCommand();
  vlayer->triggerRepaint();
}

void QgsMapToolScaleFeature::keyReleaseEvent( QKeyEvent *e )
{
  if ( mScalingActive && e->key() == Qt::Key_Escape )
  {
    cancel();
    return;
  }
  QgsMapToolAdvancedDigitizing::keyReleaseEvent( e );
}

void QgsMapToolScaleFeature::activate()
{
  QgsVectorLayer *vlayer = currentVectorLayer();
  if ( !vlayer )
  {
    return;
  }

  if ( !vlayer->isEditable() )
  {
    return;
  }

  if ( vlayer->selectedFeatureCount() > 0 )
  {
    mExtent = vlayer->boundingBoxOfSelected();
    mFeatureCenterMapCoords = toMapCoordinates( vlayer, mExtent.center() );

    mAnchorPoint = std::make_unique<QgsVertexMarker>( mCanvas );
    mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
    mAnchorPoint->setCenter( mFeatureCenterMapCoords );
  }
  QgsMapToolAdvancedDigitizing::activate();
}

void QgsMapToolScaleFeature::deleteRubberband()
{
  delete mRubberBand;
  mRubberBand = nullptr;

  mOriginalGeometries.clear();
}

void QgsMapToolScaleFeature::deactivate()
{
  deleteScalingWidget();
  mScalingActive = false;
  mAnchorPoint.reset();
  deleteRubberband();
  mSnapIndicator->setMatch( QgsPointLocator::Match() );
  QgsMapToolAdvancedDigitizing::deactivate();
}

void QgsMapToolScaleFeature::createScalingWidget()
{
  if ( !mCanvas )
  {
    return;
  }

  deleteScalingWidget();

  mScalingWidget = new QgsScaleMagnetWidget( QStringLiteral( "Scaling:" ) );
  QgisApp::instance()->addUserInputWidget( mScalingWidget );
  mScalingWidget->setFocus( Qt::TabFocusReason );

  connect( mScalingWidget, &QgsScaleMagnetWidget::scaleChanged, this, &QgsMapToolScaleFeature::updateRubberband );
  connect( mScalingWidget, &QgsScaleMagnetWidget::scaleEditingFinished, this, &QgsMapToolScaleFeature::applyScaling );
  connect( mScalingWidget, &QgsScaleMagnetWidget::scaleEditingCanceled, this, &QgsMapToolScaleFeature::cancel );
}

void QgsMapToolScaleFeature::deleteScalingWidget()
{
  if ( mScalingWidget )
  {
    disconnect( mScalingWidget, &QgsScaleMagnetWidget::scaleChanged, this, &QgsMapToolScaleFeature::updateRubberband );
    disconnect( mScalingWidget, &QgsScaleMagnetWidget::scaleEditingFinished, this, &QgsMapToolScaleFeature::applyScaling );
    disconnect( mScalingWidget, &QgsScaleMagnetWidget::scaleEditingCanceled, this, &QgsMapToolScaleFeature::cancel );

    mScalingWidget->releaseKeyboard();
    mScalingWidget->deleteLater();
  }
  mScalingWidget = nullptr;
}

        和移动要素类似,也是继承了QgsMapToolAdvancedDigitizing,然后在cadCanvasReleaseEvent和cadCanvasMoveEvent中实现缩放效果,流程也类似,创建一个rubberband,然后鼠标点击后进入缩放编辑状态,移动的时候根据鼠标距离中心锚点mAnchorPoint的位置来动态改变要素的尺寸。

        这里需要多说一句,和移动要素不同,缩放要素中多了一个锚点定位的功能,具体代码参考cadCanvasReleaseEvent的下面一段,这里通过判断CTRL是否按下来决定锚点是否要进行调整。

  // place anchor point on CTRL + click
  if ( e->modifiers() & Qt::ControlModifier )
  {
    if ( !mAnchorPoint )
    {
      mAnchorPoint = std::make_unique<QgsVertexMarker>( mCanvas );
      mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
    }
    mAnchorPoint->setCenter( e->mapPoint() );
    mFeatureCenterMapCoords = e->mapPoint();
    cadDockWidget()->clear();
    return;
  }

3 代码调整

        里面同样有一些我们不需要的功能,比如QgsScaleMagnetWidget的实现和调用可以移除,cadDockWidget相关调用也可以移除,QgsSnapIndicator也没用,所以我们移除头文件中变量和函数:

    void createScalingWidget();
    void deleteScalingWidget();

    //! Snapping indicators
    std::unique_ptr<QgsSnapIndicator> mSnapIndicator;

    //! Shows current scale value and allows numerical editing
    QgsScaleMagnetWidget *mScalingWidget = nullptr;

        同样的,构造函数里要把dockWidget做为参数传入,:

QgsMapToolScaleFeature::QgsMapToolScaleFeature( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget* dock)
    : QgsMapToolAdvancedDigitizing( canvas, dock)
{
}

4 如何调用

        qgsadvanceddigitizingdockwidgetbase.ui,qgsadvanceddigitizingfloaterbase.ui,这两个ui样式文件需要拷贝到工程目录下,因为QgsMapToolAdvancedDigitizing这个类编译的时候需要。

        还是以创建的MainWindow实例为例,在头文件中添加变量:

    QgsMapToolScaleFeature* m_mapScaleFeature;
    QgsAdvancedDigitizingDockWidget *m_pDockWidget;

构造函数里添加:

m_mapScaleFeature = new QgsMapToolScaleFeature(m_mapCanvas, m_pDockWidget);

调用时设置maptool

m_mapCanvas->setMapTool(m_mapScaleFeature);//将工具添加到m_mapcanvas上

5 最后效果

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值