mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-06 00:03:16 -05:00
After main map canvas render is complete, render a separate image for each adjacent map "tile" (in the background). These are shown when panning the map to display a preview of what will be visible when the panning operation ends.
2179 lines
56 KiB
C++
2179 lines
56 KiB
C++
/***************************************************************************
|
|
qgsmapcanvas.cpp - description
|
|
------------------ -
|
|
begin : Sun Jun 30 2002
|
|
copyright : ( C ) 2002 by Gary E.Sherman
|
|
email : sherman at mrcc.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 <QtGlobal>
|
|
#include <QApplication>
|
|
#include <QCursor>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QGraphicsItem>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsView>
|
|
#include <QKeyEvent>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPaintEvent>
|
|
#include <QPixmap>
|
|
#include <QRect>
|
|
#include <QTextStream>
|
|
#include <QResizeEvent>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QWheelEvent>
|
|
|
|
#include "qgis.h"
|
|
#include "qgssettings.h"
|
|
#include "qgsmapcanvasannotationitem.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgsexception.h"
|
|
#include "qgsdatumtransformdialog.h"
|
|
#include "qgsfeatureiterator.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsmapcanvas.h"
|
|
#include "qgsmapcanvasmap.h"
|
|
#include "qgsmapcanvassnappingutils.h"
|
|
#include "qgsmaplayer.h"
|
|
#include "qgsmaptoolpan.h"
|
|
#include "qgsmaptoolzoom.h"
|
|
#include "qgsmaptopixel.h"
|
|
#include "qgsmapoverviewcanvas.h"
|
|
#include "qgsmaprenderercache.h"
|
|
#include "qgsmaprenderercustompainterjob.h"
|
|
#include "qgsmaprendererparalleljob.h"
|
|
#include "qgsmaprenderersequentialjob.h"
|
|
#include "qgsmapsettingsutils.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgsmessageviewer.h"
|
|
#include "qgspallabeling.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsrubberband.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgscursors.h"
|
|
#include "qgsmapthemecollection.h"
|
|
#include <cmath>
|
|
|
|
|
|
/** \ingroup gui
|
|
* Deprecated to be deleted, stuff from here should be moved elsewhere.
|
|
* @note not available in Python bindings
|
|
*/
|
|
//TODO QGIS 3.0 - remove
|
|
class QgsMapCanvas::CanvasProperties
|
|
{
|
|
public:
|
|
CanvasProperties()
|
|
: mouseButtonDown( false )
|
|
, panSelectorDown( false )
|
|
{ }
|
|
|
|
//!Flag to indicate status of mouse button
|
|
bool mouseButtonDown;
|
|
|
|
//! Last seen point of the mouse
|
|
QPoint mouseLastXY;
|
|
|
|
//! Beginning point of a rubber band
|
|
QPoint rubberStartPoint;
|
|
|
|
//! Flag to indicate the pan selector key is held down by user
|
|
bool panSelectorDown;
|
|
};
|
|
|
|
|
|
|
|
QgsMapCanvas::QgsMapCanvas( QWidget *parent )
|
|
: QGraphicsView( parent )
|
|
, mCanvasProperties( new CanvasProperties )
|
|
, mMap( nullptr )
|
|
, mFrozen( false )
|
|
, mRefreshScheduled( false )
|
|
, mRenderFlag( true ) // by default, the canvas is rendered
|
|
, mCurrentLayer( nullptr )
|
|
, mScene( nullptr )
|
|
, mMapTool( nullptr )
|
|
, mLastNonZoomMapTool( nullptr )
|
|
, mLastExtentIndex( -1 )
|
|
, mWheelZoomFactor( 2.0 )
|
|
, mJob( nullptr )
|
|
, mJobCanceled( false )
|
|
, mLabelingResults( nullptr )
|
|
, mUseParallelRendering( false )
|
|
, mDrawRenderingStats( false )
|
|
, mCache( nullptr )
|
|
, mResizeTimer( nullptr )
|
|
, mPreviewEffect( nullptr )
|
|
, mSnappingUtils( nullptr )
|
|
, mScaleLocked( false )
|
|
, mExpressionContextScope( tr( "Map Canvas" ) )
|
|
, mZoomDragging( false )
|
|
{
|
|
mScene = new QGraphicsScene();
|
|
setScene( mScene );
|
|
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
setMouseTracking( true );
|
|
setFocusPolicy( Qt::StrongFocus );
|
|
|
|
mResizeTimer = new QTimer( this );
|
|
mResizeTimer->setSingleShot( true );
|
|
connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
|
|
|
|
// create map canvas item which will show the map
|
|
mMap = new QgsMapCanvasMap( this );
|
|
|
|
// project handling
|
|
connect( QgsProject::instance(), &QgsProject::readProject,
|
|
this, &QgsMapCanvas::readProject );
|
|
connect( QgsProject::instance(), &QgsProject::writeProject,
|
|
this, &QgsMapCanvas::writeProject );
|
|
|
|
connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
|
|
connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
|
|
|
|
mSettings.setFlag( QgsMapSettings::DrawEditingInfo );
|
|
mSettings.setFlag( QgsMapSettings::UseRenderingOptimization );
|
|
mSettings.setFlag( QgsMapSettings::RenderPartialOutput );
|
|
mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
|
|
connect( QgsProject::instance(), &QgsProject::ellipsoidChanged,
|
|
this, [ = ]
|
|
{
|
|
mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
|
|
} );
|
|
|
|
//segmentation parameters
|
|
QgsSettings settings;
|
|
double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
|
|
QgsAbstractGeometry::SegmentationToleranceType toleranceType = QgsAbstractGeometry::SegmentationToleranceType( settings.value( QStringLiteral( "qgis/segmentationToleranceType" ), 0 ).toInt() );
|
|
mSettings.setSegmentationTolerance( segmentationTolerance );
|
|
mSettings.setSegmentationToleranceType( toleranceType );
|
|
|
|
mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
|
|
|
|
QSize s = viewport()->size();
|
|
mSettings.setOutputSize( s );
|
|
setSceneRect( 0, 0, s.width(), s.height() );
|
|
mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
|
|
|
|
moveCanvasContents( true );
|
|
|
|
connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
|
|
mMapUpdateTimer.setInterval( 250 );
|
|
|
|
#ifdef Q_OS_WIN
|
|
// Enable touch event on Windows.
|
|
// Qt on Windows needs to be told it can take touch events or else it ignores them.
|
|
grabGesture( Qt::PinchGesture );
|
|
viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
|
|
#endif
|
|
|
|
mPreviewEffect = new QgsPreviewEffect( this );
|
|
viewport()->setGraphicsEffect( mPreviewEffect );
|
|
|
|
QPixmap zoomPixmap = QPixmap( ( const char ** )( zoom_in ) );
|
|
mZoomCursor = QCursor( zoomPixmap, 7, 7 );
|
|
|
|
connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
|
|
|
|
connect( this, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvas::updateCanvasItemPositions );
|
|
|
|
setInteractive( false );
|
|
|
|
refresh();
|
|
|
|
} // QgsMapCanvas ctor
|
|
|
|
|
|
QgsMapCanvas::~QgsMapCanvas()
|
|
{
|
|
if ( mMapTool )
|
|
{
|
|
mMapTool->deactivate();
|
|
mMapTool = nullptr;
|
|
}
|
|
mLastNonZoomMapTool = nullptr;
|
|
|
|
// rendering job may still end up writing into canvas map item
|
|
// so kill it before deleting canvas items
|
|
if ( mJob )
|
|
{
|
|
whileBlocking( mJob )->cancel();
|
|
delete mJob;
|
|
}
|
|
|
|
// delete canvas items prior to deleting the canvas
|
|
// because they might try to update canvas when it's
|
|
// already being destructed, ends with segfault
|
|
QList<QGraphicsItem *> list = mScene->items();
|
|
QList<QGraphicsItem *>::iterator it = list.begin();
|
|
while ( it != list.end() )
|
|
{
|
|
QGraphicsItem *item = *it;
|
|
delete item;
|
|
++it;
|
|
}
|
|
|
|
mScene->deleteLater(); // crashes in python tests on windows
|
|
|
|
// mCanvasProperties auto-deleted via QScopedPointer
|
|
// CanvasProperties struct has its own dtor for freeing resources
|
|
|
|
delete mCache;
|
|
|
|
delete mLabelingResults;
|
|
|
|
} // dtor
|
|
|
|
void QgsMapCanvas::setMagnificationFactor( double factor )
|
|
{
|
|
// do not go higher or lower than min max magnification ratio
|
|
double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
|
|
double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
|
|
factor = qBound( magnifierMin, factor, magnifierMax );
|
|
|
|
// the magnifier widget is in integer percent
|
|
if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
|
|
{
|
|
mSettings.setMagnificationFactor( factor );
|
|
refresh();
|
|
emit magnificationChanged( factor );
|
|
}
|
|
}
|
|
|
|
double QgsMapCanvas::magnificationFactor() const
|
|
{
|
|
return mSettings.magnificationFactor();
|
|
}
|
|
|
|
void QgsMapCanvas::enableAntiAliasing( bool flag )
|
|
{
|
|
mSettings.setFlag( QgsMapSettings::Antialiasing, flag );
|
|
} // anti aliasing
|
|
|
|
void QgsMapCanvas::enableMapTileRendering( bool flag )
|
|
{
|
|
mSettings.setFlag( QgsMapSettings::RenderMapTile, flag );
|
|
}
|
|
|
|
QgsMapLayer *QgsMapCanvas::layer( int index )
|
|
{
|
|
QList<QgsMapLayer *> layers = mapSettings().layers();
|
|
if ( index >= 0 && index < ( int ) layers.size() )
|
|
return layers[index];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void QgsMapCanvas::setCurrentLayer( QgsMapLayer *layer )
|
|
{
|
|
mCurrentLayer = layer;
|
|
emit currentLayerChanged( layer );
|
|
}
|
|
|
|
double QgsMapCanvas::scale() const
|
|
{
|
|
return mapSettings().scale();
|
|
}
|
|
|
|
bool QgsMapCanvas::isDrawing()
|
|
{
|
|
return nullptr != mJob;
|
|
} // isDrawing
|
|
|
|
// return the current coordinate transform based on the extents and
|
|
// device size
|
|
const QgsMapToPixel *QgsMapCanvas::getCoordinateTransform()
|
|
{
|
|
return &mapSettings().mapToPixel();
|
|
}
|
|
|
|
void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
|
|
{
|
|
// following a theme => request denied!
|
|
if ( !mTheme.isEmpty() )
|
|
return;
|
|
|
|
setLayersPrivate( layers );
|
|
}
|
|
|
|
void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
|
|
{
|
|
QList<QgsMapLayer *> oldLayers = mSettings.layers();
|
|
|
|
// update only if needed
|
|
if ( layers == oldLayers )
|
|
return;
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, oldLayers )
|
|
{
|
|
disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
|
|
disconnect( layer, &QgsMapLayer::crsChanged, this, &QgsMapCanvas::layerCrsChange );
|
|
disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
|
|
if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
|
|
{
|
|
disconnect( vlayer, &QgsVectorLayer::selectionChanged, this, &QgsMapCanvas::selectionChangedSlot );
|
|
}
|
|
}
|
|
|
|
mSettings.setLayers( layers );
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, layers )
|
|
{
|
|
if ( !layer )
|
|
continue;
|
|
connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
|
|
connect( layer, &QgsMapLayer::crsChanged, this, &QgsMapCanvas::layerCrsChange );
|
|
connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
|
|
if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
|
|
{
|
|
connect( vlayer, &QgsVectorLayer::selectionChanged, this, &QgsMapCanvas::selectionChangedSlot );
|
|
}
|
|
}
|
|
updateDatumTransformEntries();
|
|
|
|
QgsDebugMsg( "Layers have changed, refreshing" );
|
|
emit layersChanged();
|
|
|
|
updateAutoRefreshTimer();
|
|
refresh();
|
|
}
|
|
|
|
|
|
const QgsMapSettings &QgsMapCanvas::mapSettings() const
|
|
{
|
|
return mSettings;
|
|
}
|
|
|
|
void QgsMapCanvas::setDestinationCrs( const QgsCoordinateReferenceSystem &crs )
|
|
{
|
|
if ( mSettings.destinationCrs() == crs )
|
|
return;
|
|
|
|
// try to reproject current extent to the new one
|
|
QgsRectangle rect;
|
|
if ( !mSettings.visibleExtent().isEmpty() )
|
|
{
|
|
QgsCoordinateTransform transform( mSettings.destinationCrs(), crs );
|
|
try
|
|
{
|
|
rect = transform.transformBoundingBox( mSettings.visibleExtent() );
|
|
}
|
|
catch ( QgsCsException &e )
|
|
{
|
|
Q_UNUSED( e );
|
|
QgsDebugMsg( QString( "Transform error caught: %1" ).arg( e.what() ) );
|
|
}
|
|
}
|
|
|
|
if ( !rect.isEmpty() )
|
|
{
|
|
setExtent( rect );
|
|
}
|
|
|
|
mSettings.setDestinationCrs( crs );
|
|
updateScale();
|
|
|
|
QgsDebugMsg( "refreshing after destination CRS changed" );
|
|
refresh();
|
|
|
|
updateDatumTransformEntries();
|
|
|
|
emit destinationCrsChanged();
|
|
}
|
|
|
|
void QgsMapCanvas::setMapSettingsFlags( QgsMapSettings::Flags flags )
|
|
{
|
|
mSettings.setFlags( flags );
|
|
clearCache();
|
|
refresh();
|
|
}
|
|
|
|
const QgsLabelingResults *QgsMapCanvas::labelingResults() const
|
|
{
|
|
return mLabelingResults;
|
|
}
|
|
|
|
void QgsMapCanvas::setCachingEnabled( bool enabled )
|
|
{
|
|
if ( enabled == isCachingEnabled() )
|
|
return;
|
|
|
|
if ( mJob && mJob->isActive() )
|
|
{
|
|
// wait for the current rendering to finish, before touching the cache
|
|
mJob->waitForFinished();
|
|
}
|
|
|
|
if ( enabled )
|
|
{
|
|
mCache = new QgsMapRendererCache;
|
|
}
|
|
else
|
|
{
|
|
delete mCache;
|
|
mCache = nullptr;
|
|
}
|
|
}
|
|
|
|
bool QgsMapCanvas::isCachingEnabled() const
|
|
{
|
|
return nullptr != mCache;
|
|
}
|
|
|
|
void QgsMapCanvas::clearCache()
|
|
{
|
|
if ( mCache )
|
|
mCache->clear();
|
|
}
|
|
|
|
void QgsMapCanvas::setParallelRenderingEnabled( bool enabled )
|
|
{
|
|
mUseParallelRendering = enabled;
|
|
}
|
|
|
|
bool QgsMapCanvas::isParallelRenderingEnabled() const
|
|
{
|
|
return mUseParallelRendering;
|
|
}
|
|
|
|
void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
|
|
{
|
|
mMapUpdateTimer.setInterval( timeMilliseconds );
|
|
}
|
|
|
|
int QgsMapCanvas::mapUpdateInterval() const
|
|
{
|
|
return mMapUpdateTimer.interval();
|
|
}
|
|
|
|
|
|
QgsMapLayer *QgsMapCanvas::currentLayer()
|
|
{
|
|
return mCurrentLayer;
|
|
}
|
|
|
|
|
|
void QgsMapCanvas::refresh()
|
|
{
|
|
if ( !mSettings.hasValidSettings() )
|
|
{
|
|
QgsDebugMsg( "CANVAS refresh - invalid settings -> nothing to do" );
|
|
return;
|
|
}
|
|
|
|
if ( !mRenderFlag || mFrozen )
|
|
{
|
|
QgsDebugMsg( "CANVAS render flag off" );
|
|
return;
|
|
}
|
|
|
|
if ( mRefreshScheduled )
|
|
{
|
|
QgsDebugMsg( "CANVAS refresh already scheduled" );
|
|
return;
|
|
}
|
|
|
|
mRefreshScheduled = true;
|
|
|
|
QgsDebugMsg( "CANVAS refresh scheduling" );
|
|
|
|
// schedule a refresh
|
|
QTimer::singleShot( 1, this, SLOT( refreshMap() ) );
|
|
} // refresh
|
|
|
|
void QgsMapCanvas::refreshMap()
|
|
{
|
|
Q_ASSERT( mRefreshScheduled );
|
|
|
|
QgsDebugMsgLevel( "CANVAS refresh!", 3 );
|
|
|
|
stopRendering(); // if any...
|
|
stopPreviewJobs();
|
|
|
|
//build the expression context
|
|
QgsExpressionContext expressionContext;
|
|
expressionContext << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
|
|
<< QgsExpressionContextUtils::mapSettingsScope( mSettings )
|
|
<< new QgsExpressionContextScope( mExpressionContextScope );
|
|
|
|
mSettings.setExpressionContext( expressionContext );
|
|
|
|
if ( !mTheme.isEmpty() )
|
|
{
|
|
// IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
|
|
// comment) retrieving layer styles from the theme collection gives an XML snapshot of the
|
|
// current state of the style. If we had stored the style overrides earlier (such as in
|
|
// mapThemeChanged slot) then this xml could be out of date...
|
|
// TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
|
|
// just return the style name, we can instead set the overrides in mapThemeChanged and not here
|
|
mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
|
|
}
|
|
|
|
// create the renderer job
|
|
Q_ASSERT( !mJob );
|
|
mJobCanceled = false;
|
|
if ( mUseParallelRendering )
|
|
mJob = new QgsMapRendererParallelJob( mSettings );
|
|
else
|
|
mJob = new QgsMapRendererSequentialJob( mSettings );
|
|
connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
|
|
mJob->setCache( mCache );
|
|
|
|
mJob->start();
|
|
|
|
// from now on we can accept refresh requests again
|
|
// this must be reset only after the job has been started, because
|
|
// some providers (yes, it's you WCS and AMS!) during preparation
|
|
// do network requests and start an internal event loop, which may
|
|
// end up calling refresh() and would schedule another refresh,
|
|
// deleting the one we have just started.
|
|
mRefreshScheduled = false;
|
|
|
|
mMapUpdateTimer.start();
|
|
|
|
emit renderStarting();
|
|
}
|
|
|
|
void QgsMapCanvas::mapThemeChanged( const QString &theme )
|
|
{
|
|
if ( theme == mTheme )
|
|
{
|
|
// set the canvas layers to match the new layers contained in the map theme
|
|
// NOTE: we do this when the theme layers change and not when we are refreshing the map
|
|
// as setLayers() sets up necessary connections to handle changes to the layers
|
|
setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
|
|
// IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
|
|
// comment) retrieving layer styles from the theme collection gives an XML snapshot of the
|
|
// current state of the style. If changes were made to the style then this xml
|
|
// snapshot goes out of sync...
|
|
// TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
|
|
// just return the style name, we can instead set the overrides here and not in refreshMap()
|
|
|
|
clearCache();
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
|
|
void QgsMapCanvas::rendererJobFinished()
|
|
{
|
|
QgsDebugMsg( QString( "CANVAS finish! %1" ).arg( !mJobCanceled ) );
|
|
|
|
mMapUpdateTimer.stop();
|
|
|
|
// TODO: would be better to show the errors in message bar
|
|
Q_FOREACH ( const QgsMapRendererJob::Error &error, mJob->errors() )
|
|
{
|
|
QgsMessageLog::logMessage( error.layerID + " :: " + error.message, tr( "Rendering" ) );
|
|
}
|
|
|
|
if ( !mJobCanceled )
|
|
{
|
|
// take labeling results before emitting renderComplete, so labeling map tools
|
|
// connected to signal work with correct results
|
|
if ( !mJob->usedCachedLabels() )
|
|
{
|
|
delete mLabelingResults;
|
|
mLabelingResults = mJob->takeLabelingResults();
|
|
}
|
|
|
|
QImage img = mJob->renderedImage();
|
|
|
|
// emit renderComplete to get our decorations drawn
|
|
QPainter p( &img );
|
|
emit renderComplete( &p );
|
|
|
|
QgsSettings settings;
|
|
if ( settings.value( QStringLiteral( "Map/logCanvasRefreshEvent" ), false ).toBool() )
|
|
{
|
|
QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
|
|
QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
|
|
}
|
|
|
|
if ( mDrawRenderingStats )
|
|
{
|
|
int w = img.width(), h = img.height();
|
|
QFont fnt = p.font();
|
|
fnt.setBold( true );
|
|
p.setFont( fnt );
|
|
int lh = p.fontMetrics().height() * 2;
|
|
QRect r( 0, h - lh, w, lh );
|
|
p.setPen( Qt::NoPen );
|
|
p.setBrush( QColor( 0, 0, 0, 110 ) );
|
|
p.drawRect( r );
|
|
p.setPen( Qt::white );
|
|
QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? "PARALLEL" : "SEQUENTIAL" ).arg( mJob->renderingTime() );
|
|
p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
|
|
}
|
|
|
|
p.end();
|
|
|
|
mMap->setContent( img, imageRect( img, mSettings ) );
|
|
startPreviewJobs();
|
|
}
|
|
|
|
// now we are in a slot called from mJob - do not delete it immediately
|
|
// so the class is still valid when the execution returns to the class
|
|
mJob->deleteLater();
|
|
mJob = nullptr;
|
|
|
|
emit mapCanvasRefreshed();
|
|
}
|
|
|
|
void QgsMapCanvas::previewJobFinished()
|
|
{
|
|
QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
|
|
if ( !job )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( mMap )
|
|
{
|
|
mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
|
|
}
|
|
}
|
|
|
|
QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
|
|
{
|
|
// This is a hack to pass QgsMapCanvasItem::setRect what it
|
|
// expects (encoding of position and size of the item)
|
|
const QgsMapToPixel &m2p = mapSettings.mapToPixel();
|
|
QgsPointXY topLeft = m2p.toMapPoint( 0, 0 );
|
|
double res = m2p.mapUnitsPerPixel();
|
|
QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
|
|
return rect;
|
|
}
|
|
|
|
void QgsMapCanvas::mapUpdateTimeout()
|
|
{
|
|
if ( mJob )
|
|
{
|
|
const QImage &img = mJob->renderedImage();
|
|
mMap->setContent( img, imageRect( img, mSettings ) );
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::stopRendering()
|
|
{
|
|
if ( mJob )
|
|
{
|
|
QgsDebugMsg( "CANVAS stop rendering!" );
|
|
mJobCanceled = true;
|
|
disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
|
|
connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
|
|
mJob->cancelWithoutBlocking();
|
|
mJob = nullptr;
|
|
}
|
|
}
|
|
|
|
//the format defaults to "PNG" if not specified
|
|
void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
|
|
{
|
|
QPainter painter;
|
|
QImage image;
|
|
|
|
//
|
|
//check if the optional QPaintDevice was supplied
|
|
//
|
|
if ( theQPixmap )
|
|
{
|
|
image = theQPixmap->toImage();
|
|
painter.begin( &image );
|
|
|
|
// render
|
|
QgsMapRendererCustomPainterJob job( mSettings, &painter );
|
|
job.start();
|
|
job.waitForFinished();
|
|
emit renderComplete( &painter );
|
|
}
|
|
else //use the map view
|
|
{
|
|
image = mMap->contentImage().copy();
|
|
painter.begin( &image );
|
|
}
|
|
|
|
// draw annotations
|
|
QStyleOptionGraphicsItem option;
|
|
option.initFrom( this );
|
|
QGraphicsItem *item = nullptr;
|
|
QListIterator<QGraphicsItem *> i( items() );
|
|
i.toBack();
|
|
while ( i.hasPrevious() )
|
|
{
|
|
item = i.previous();
|
|
|
|
if ( !item || dynamic_cast< QgsMapCanvasAnnotationItem * >( item ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
painter.save();
|
|
|
|
QPointF itemScenePos = item->scenePos();
|
|
painter.translate( itemScenePos.x(), itemScenePos.y() );
|
|
|
|
item->paint( &painter, &option );
|
|
|
|
painter.restore();
|
|
}
|
|
|
|
painter.end();
|
|
image.save( fileName, format.toLocal8Bit().data() );
|
|
|
|
QFileInfo myInfo = QFileInfo( fileName );
|
|
|
|
// build the world file name
|
|
QString outputSuffix = myInfo.suffix();
|
|
QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.baseName() + '.'
|
|
+ outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
|
|
QFile myWorldFile( myWorldFileName );
|
|
if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
|
|
{
|
|
return;
|
|
}
|
|
QTextStream myStream( &myWorldFile );
|
|
myStream << QgsMapSettingsUtils::worldFileContent( mapSettings() );
|
|
} // saveAsImage
|
|
|
|
|
|
|
|
QgsRectangle QgsMapCanvas::extent() const
|
|
{
|
|
return mapSettings().visibleExtent();
|
|
} // extent
|
|
|
|
QgsRectangle QgsMapCanvas::fullExtent() const
|
|
{
|
|
return mapSettings().fullExtent();
|
|
} // extent
|
|
|
|
|
|
void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
|
|
{
|
|
QgsRectangle current = extent();
|
|
|
|
if ( ( r == current ) && magnified )
|
|
return;
|
|
|
|
if ( r.isEmpty() )
|
|
{
|
|
if ( !mSettings.hasValidSettings() )
|
|
{
|
|
// we can't even just move the map center
|
|
QgsDebugMsg( "Empty extent - ignoring" );
|
|
return;
|
|
}
|
|
|
|
// ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
|
|
QgsDebugMsg( "Empty extent - keeping old scale with new center!" );
|
|
setCenter( r.center() );
|
|
}
|
|
else
|
|
{
|
|
mSettings.setExtent( r, magnified );
|
|
}
|
|
emit extentsChanged();
|
|
updateScale();
|
|
if ( mLastExtent.size() > 20 )
|
|
mLastExtent.removeAt( 0 );
|
|
|
|
//clear all extent items after current index
|
|
for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
|
|
{
|
|
mLastExtent.removeAt( i );
|
|
}
|
|
|
|
mLastExtent.append( extent() );
|
|
|
|
// adjust history to no more than 20
|
|
if ( mLastExtent.size() > 20 )
|
|
{
|
|
mLastExtent.removeAt( 0 );
|
|
}
|
|
|
|
// the last item is the current extent
|
|
mLastExtentIndex = mLastExtent.size() - 1;
|
|
|
|
// update controls' enabled state
|
|
emit zoomLastStatusChanged( mLastExtentIndex > 0 );
|
|
emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
|
|
} // setExtent
|
|
|
|
void QgsMapCanvas::setCenter( const QgsPointXY ¢er )
|
|
{
|
|
QgsRectangle r = mapSettings().extent();
|
|
double x = center.x();
|
|
double y = center.y();
|
|
setExtent(
|
|
QgsRectangle(
|
|
x - r.width() / 2.0, y - r.height() / 2.0,
|
|
x + r.width() / 2.0, y + r.height() / 2.0
|
|
),
|
|
true
|
|
);
|
|
} // setCenter
|
|
|
|
QgsPointXY QgsMapCanvas::center() const
|
|
{
|
|
QgsRectangle r = mapSettings().extent();
|
|
return r.center();
|
|
}
|
|
|
|
|
|
double QgsMapCanvas::rotation() const
|
|
{
|
|
return mapSettings().rotation();
|
|
} // rotation
|
|
|
|
void QgsMapCanvas::setRotation( double degrees )
|
|
{
|
|
double current = rotation();
|
|
|
|
if ( degrees == current )
|
|
return;
|
|
|
|
mSettings.setRotation( degrees );
|
|
emit rotationChanged( degrees );
|
|
emit extentsChanged(); // visible extent changes with rotation
|
|
} // setRotation
|
|
|
|
|
|
void QgsMapCanvas::updateScale()
|
|
{
|
|
emit scaleChanged( mapSettings().scale() );
|
|
}
|
|
|
|
|
|
void QgsMapCanvas::zoomToFullExtent()
|
|
{
|
|
QgsRectangle extent = fullExtent();
|
|
// If the full extent is an empty set, don't do the zoom
|
|
if ( !extent.isEmpty() )
|
|
{
|
|
// Add a 5% margin around the full extent
|
|
extent.scale( 1.05 );
|
|
setExtent( extent );
|
|
}
|
|
refresh();
|
|
|
|
} // zoomToFullExtent
|
|
|
|
|
|
|
|
void QgsMapCanvas::zoomToPreviousExtent()
|
|
{
|
|
if ( mLastExtentIndex > 0 )
|
|
{
|
|
mLastExtentIndex--;
|
|
mSettings.setExtent( mLastExtent[mLastExtentIndex] );
|
|
emit extentsChanged();
|
|
updateScale();
|
|
refresh();
|
|
// update controls' enabled state
|
|
emit zoomLastStatusChanged( mLastExtentIndex > 0 );
|
|
emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
|
|
}
|
|
|
|
} // zoomToPreviousExtent
|
|
|
|
void QgsMapCanvas::zoomToNextExtent()
|
|
{
|
|
if ( mLastExtentIndex < mLastExtent.size() - 1 )
|
|
{
|
|
mLastExtentIndex++;
|
|
mSettings.setExtent( mLastExtent[mLastExtentIndex] );
|
|
emit extentsChanged();
|
|
updateScale();
|
|
refresh();
|
|
// update controls' enabled state
|
|
emit zoomLastStatusChanged( mLastExtentIndex > 0 );
|
|
emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
|
|
}
|
|
}// zoomToNextExtent
|
|
|
|
void QgsMapCanvas::clearExtentHistory()
|
|
{
|
|
mLastExtent.clear(); // clear the zoom history list
|
|
mLastExtent.append( extent() ) ; // set the current extent in the list
|
|
mLastExtentIndex = mLastExtent.size() - 1;
|
|
// update controls' enabled state
|
|
emit zoomLastStatusChanged( mLastExtentIndex > 0 );
|
|
emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
|
|
}// clearExtentHistory
|
|
|
|
void QgsMapCanvas::zoomToSelected( QgsVectorLayer *layer )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
// use current layer by default
|
|
layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
|
|
}
|
|
|
|
if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
|
|
return;
|
|
|
|
QgsRectangle rect = layer->boundingBoxOfSelected();
|
|
if ( rect.isNull() )
|
|
{
|
|
emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
rect = mapSettings().layerExtentToOutputExtent( layer, rect );
|
|
zoomToFeatureExtent( rect );
|
|
} // zoomToSelected
|
|
|
|
void QgsMapCanvas::zoomToFeatureExtent( QgsRectangle &rect )
|
|
{
|
|
// no selected features, only one selected point feature
|
|
//or two point features with the same x- or y-coordinates
|
|
if ( rect.isEmpty() )
|
|
{
|
|
// zoom in
|
|
QgsPointXY c = rect.center();
|
|
rect = extent();
|
|
rect.scale( 1.0, &c );
|
|
}
|
|
//zoom to an area
|
|
else
|
|
{
|
|
// Expand rect to give a bit of space around the selected
|
|
// objects so as to keep them clear of the map boundaries
|
|
// The same 5% should apply to all margins.
|
|
rect.scale( 1.05 );
|
|
}
|
|
|
|
setExtent( rect );
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::zoomToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsRectangle bbox;
|
|
QString errorMsg;
|
|
if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
|
|
{
|
|
zoomToFeatureExtent( bbox );
|
|
}
|
|
else
|
|
{
|
|
emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, QgsMessageBar::WARNING );
|
|
}
|
|
|
|
}
|
|
|
|
void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsRectangle bbox;
|
|
QString errorMsg;
|
|
if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
|
|
{
|
|
setCenter( bbox.center() );
|
|
refresh();
|
|
}
|
|
else
|
|
{
|
|
emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, QgsMessageBar::WARNING );
|
|
}
|
|
}
|
|
|
|
bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
|
|
{
|
|
QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setSubsetOfAttributes( QgsAttributeList() ) );
|
|
bbox.setMinimal();
|
|
QgsFeature fet;
|
|
int featureCount = 0;
|
|
errorMsg.clear();
|
|
|
|
while ( it.nextFeature( fet ) )
|
|
{
|
|
QgsGeometry geom = fet.geometry();
|
|
if ( geom.isNull() )
|
|
{
|
|
errorMsg = tr( "Feature does not have a geometry" );
|
|
}
|
|
else if ( geom.geometry()->isEmpty() )
|
|
{
|
|
errorMsg = tr( "Feature geometry is empty" );
|
|
}
|
|
if ( !errorMsg.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
QgsRectangle r = mapSettings().layerExtentToOutputExtent( layer, geom.boundingBox() );
|
|
bbox.combineExtentWith( r );
|
|
featureCount++;
|
|
}
|
|
|
|
if ( featureCount != ids.count() )
|
|
{
|
|
errorMsg = tr( "Feature not found" );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void QgsMapCanvas::panToSelected( QgsVectorLayer *layer )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
// use current layer by default
|
|
layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
|
|
}
|
|
|
|
if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
|
|
return;
|
|
|
|
QgsRectangle rect = layer->boundingBoxOfSelected();
|
|
if ( rect.isNull() )
|
|
{
|
|
emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
rect = mapSettings().layerExtentToOutputExtent( layer, rect );
|
|
setCenter( rect.center() );
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
|
|
{
|
|
if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
|
|
{
|
|
emit keyPressed( e );
|
|
return;
|
|
}
|
|
|
|
if ( ! mCanvasProperties->mouseButtonDown )
|
|
{
|
|
// Don't want to interfer with mouse events
|
|
|
|
QgsRectangle currentExtent = mapSettings().visibleExtent();
|
|
double dx = qAbs( currentExtent.width() / 4 );
|
|
double dy = qAbs( currentExtent.height() / 4 );
|
|
|
|
switch ( e->key() )
|
|
{
|
|
case Qt::Key_Left:
|
|
QgsDebugMsg( "Pan left" );
|
|
setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
|
|
refresh();
|
|
break;
|
|
|
|
case Qt::Key_Right:
|
|
QgsDebugMsg( "Pan right" );
|
|
setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
|
|
refresh();
|
|
break;
|
|
|
|
case Qt::Key_Up:
|
|
QgsDebugMsg( "Pan up" );
|
|
setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
|
|
refresh();
|
|
break;
|
|
|
|
case Qt::Key_Down:
|
|
QgsDebugMsg( "Pan down" );
|
|
setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
|
|
refresh();
|
|
break;
|
|
|
|
|
|
|
|
case Qt::Key_Space:
|
|
QgsDebugMsg( "Pressing pan selector" );
|
|
|
|
//mCanvasProperties->dragging = true;
|
|
if ( ! e->isAutoRepeat() )
|
|
{
|
|
QApplication::setOverrideCursor( Qt::ClosedHandCursor );
|
|
mCanvasProperties->panSelectorDown = true;
|
|
mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
|
|
}
|
|
break;
|
|
|
|
case Qt::Key_PageUp:
|
|
QgsDebugMsg( "Zoom in" );
|
|
zoomIn();
|
|
break;
|
|
|
|
case Qt::Key_PageDown:
|
|
QgsDebugMsg( "Zoom out" );
|
|
zoomOut();
|
|
break;
|
|
|
|
#if 0
|
|
case Qt::Key_P:
|
|
mUseParallelRendering = !mUseParallelRendering;
|
|
refresh();
|
|
break;
|
|
|
|
case Qt::Key_S:
|
|
mDrawRenderingStats = !mDrawRenderingStats;
|
|
refresh();
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
// Pass it on
|
|
if ( mMapTool )
|
|
{
|
|
mMapTool->keyPressEvent( e );
|
|
}
|
|
else e->ignore();
|
|
|
|
QgsDebugMsg( "Ignoring key: " + QString::number( e->key() ) );
|
|
}
|
|
}
|
|
|
|
emit keyPressed( e );
|
|
|
|
} //keyPressEvent()
|
|
|
|
void QgsMapCanvas::keyReleaseEvent( QKeyEvent *e )
|
|
{
|
|
QgsDebugMsg( "keyRelease event" );
|
|
|
|
switch ( e->key() )
|
|
{
|
|
case Qt::Key_Space:
|
|
if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
|
|
{
|
|
QgsDebugMsg( "Releasing pan selector" );
|
|
QApplication::restoreOverrideCursor();
|
|
mCanvasProperties->panSelectorDown = false;
|
|
panActionEnd( mCanvasProperties->mouseLastXY );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Pass it on
|
|
if ( mMapTool )
|
|
{
|
|
mMapTool->keyReleaseEvent( e );
|
|
}
|
|
else e->ignore();
|
|
|
|
QgsDebugMsg( "Ignoring key release: " + QString::number( e->key() ) );
|
|
}
|
|
|
|
emit keyReleased( e );
|
|
|
|
} //keyReleaseEvent()
|
|
|
|
|
|
void QgsMapCanvas::mouseDoubleClickEvent( QMouseEvent *e )
|
|
{
|
|
// call handler of current map tool
|
|
if ( mMapTool )
|
|
{
|
|
std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
|
|
mMapTool->canvasDoubleClickEvent( me.get() );
|
|
}
|
|
}// mouseDoubleClickEvent
|
|
|
|
|
|
void QgsMapCanvas::beginZoomRect( QPoint pos )
|
|
{
|
|
mZoomRect.setRect( 0, 0, 0, 0 );
|
|
QApplication::setOverrideCursor( mZoomCursor );
|
|
mZoomDragging = true;
|
|
mZoomRubberBand.reset( new QgsRubberBand( this, QgsWkbTypes::PolygonGeometry ) );
|
|
QColor color( Qt::blue );
|
|
color.setAlpha( 63 );
|
|
mZoomRubberBand->setColor( color );
|
|
mZoomRect.setTopLeft( pos );
|
|
}
|
|
|
|
void QgsMapCanvas::endZoomRect( QPoint pos )
|
|
{
|
|
mZoomDragging = false;
|
|
mZoomRubberBand.reset( nullptr );
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
// store the rectangle
|
|
mZoomRect.setRight( pos.x() );
|
|
mZoomRect.setBottom( pos.y() );
|
|
|
|
if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
|
|
{
|
|
//probably a mistake - would result in huge zoom!
|
|
return;
|
|
}
|
|
|
|
//account for bottom right -> top left dragging
|
|
mZoomRect = mZoomRect.normalized();
|
|
|
|
// set center and zoom
|
|
const QSize &zoomRectSize = mZoomRect.size();
|
|
const QSize &canvasSize = mSettings.outputSize();
|
|
double sfx = ( double )zoomRectSize.width() / canvasSize.width();
|
|
double sfy = ( double )zoomRectSize.height() / canvasSize.height();
|
|
double sf = qMax( sfx, sfy );
|
|
|
|
QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
|
|
|
|
zoomByFactor( sf, &c );
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
|
|
{
|
|
//use middle mouse button for panning, map tools won't receive any events in that case
|
|
if ( e->button() == Qt::MidButton )
|
|
{
|
|
mCanvasProperties->panSelectorDown = true;
|
|
mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
|
|
}
|
|
else
|
|
{
|
|
// call handler of current map tool
|
|
if ( mMapTool )
|
|
{
|
|
if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
|
|
&& e->modifiers() & Qt::ShiftModifier )
|
|
{
|
|
beginZoomRect( e->pos() );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
|
|
mMapTool->canvasPressEvent( me.get() );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( mCanvasProperties->panSelectorDown )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mCanvasProperties->mouseButtonDown = true;
|
|
mCanvasProperties->rubberStartPoint = e->pos();
|
|
|
|
} // mousePressEvent
|
|
|
|
|
|
void QgsMapCanvas::mouseReleaseEvent( QMouseEvent *e )
|
|
{
|
|
//use middle mouse button for panning, map tools won't receive any events in that case
|
|
if ( e->button() == Qt::MidButton )
|
|
{
|
|
mCanvasProperties->panSelectorDown = false;
|
|
panActionEnd( mCanvasProperties->mouseLastXY );
|
|
}
|
|
else if ( e->button() == Qt::BackButton )
|
|
{
|
|
zoomToPreviousExtent();
|
|
return;
|
|
}
|
|
else if ( e->button() == Qt::ForwardButton )
|
|
{
|
|
zoomToNextExtent();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if ( mZoomDragging && e->button() == Qt::LeftButton )
|
|
{
|
|
endZoomRect( e->pos() );
|
|
return;
|
|
}
|
|
|
|
// call handler of current map tool
|
|
if ( mMapTool )
|
|
{
|
|
// right button was pressed in zoom tool? return to previous non zoom tool
|
|
if ( e->button() == Qt::RightButton && mMapTool->flags() & QgsMapTool::Transient )
|
|
{
|
|
QgsDebugMsg( "Right click in map tool zoom or pan, last tool is " +
|
|
QString( mLastNonZoomMapTool ? "not null." : "null." ) );
|
|
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
|
|
|
|
// change to older non-zoom tool
|
|
if ( mLastNonZoomMapTool
|
|
&& ( !( mLastNonZoomMapTool->flags() & QgsMapTool::EditTool )
|
|
|| ( vlayer && vlayer->isEditable() ) ) )
|
|
{
|
|
QgsMapTool *t = mLastNonZoomMapTool;
|
|
mLastNonZoomMapTool = nullptr;
|
|
setMapTool( t );
|
|
}
|
|
return;
|
|
}
|
|
std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
|
|
mMapTool->canvasReleaseEvent( me.get() );
|
|
}
|
|
}
|
|
|
|
|
|
mCanvasProperties->mouseButtonDown = false;
|
|
|
|
if ( mCanvasProperties->panSelectorDown )
|
|
return;
|
|
|
|
} // mouseReleaseEvent
|
|
|
|
void QgsMapCanvas::resizeEvent( QResizeEvent *e )
|
|
{
|
|
QGraphicsView::resizeEvent( e );
|
|
mResizeTimer->start( 500 );
|
|
|
|
QSize lastSize = viewport()->size();
|
|
|
|
mSettings.setOutputSize( lastSize );
|
|
|
|
mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
|
|
|
|
moveCanvasContents( true );
|
|
|
|
updateScale();
|
|
|
|
//refresh();
|
|
|
|
emit extentsChanged();
|
|
}
|
|
|
|
void QgsMapCanvas::paintEvent( QPaintEvent *e )
|
|
{
|
|
// no custom event handling anymore
|
|
|
|
QGraphicsView::paintEvent( e );
|
|
} // paintEvent
|
|
|
|
void QgsMapCanvas::updateCanvasItemPositions()
|
|
{
|
|
QList<QGraphicsItem *> list = mScene->items();
|
|
QList<QGraphicsItem *>::iterator it = list.begin();
|
|
while ( it != list.end() )
|
|
{
|
|
QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( *it );
|
|
|
|
if ( item )
|
|
{
|
|
item->updatePosition();
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
|
|
void QgsMapCanvas::wheelEvent( QWheelEvent *e )
|
|
{
|
|
// Zoom the map canvas in response to a mouse wheel event. Moving the
|
|
// wheel forward (away) from the user zooms in
|
|
|
|
QgsDebugMsg( "Wheel event delta " + QString::number( e->delta() ) );
|
|
|
|
if ( mMapTool )
|
|
{
|
|
mMapTool->wheelEvent( e );
|
|
if ( e->isAccepted() )
|
|
return;
|
|
}
|
|
|
|
double zoomFactor = mWheelZoomFactor;
|
|
|
|
// "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
|
|
zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * qAbs( e->angleDelta().y() );
|
|
|
|
if ( e->modifiers() & Qt::ControlModifier )
|
|
{
|
|
//holding ctrl while wheel zooming results in a finer zoom
|
|
zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
|
|
}
|
|
|
|
double signedWheelFactor = e->angleDelta().y() > 0 ? 1 / zoomFactor : zoomFactor;
|
|
|
|
// zoom map to mouse cursor by scaling
|
|
QgsPointXY oldCenter = center();
|
|
QgsPointXY mousePos( getCoordinateTransform()->toMapPoint( e->x(), e->y() ) );
|
|
QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ),
|
|
mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
|
|
|
|
zoomByFactor( signedWheelFactor, &newCenter );
|
|
}
|
|
|
|
void QgsMapCanvas::setWheelFactor( double factor )
|
|
{
|
|
mWheelZoomFactor = factor;
|
|
}
|
|
|
|
void QgsMapCanvas::zoomIn()
|
|
{
|
|
// magnification is alreday handled in zoomByFactor
|
|
zoomByFactor( 1 / mWheelZoomFactor );
|
|
}
|
|
|
|
void QgsMapCanvas::zoomOut()
|
|
{
|
|
// magnification is alreday handled in zoomByFactor
|
|
zoomByFactor( mWheelZoomFactor );
|
|
}
|
|
|
|
void QgsMapCanvas::zoomScale( double newScale )
|
|
{
|
|
zoomByFactor( newScale / scale() );
|
|
}
|
|
|
|
void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
|
|
{
|
|
double scaleFactor = ( zoomIn ? 1 / mWheelZoomFactor : mWheelZoomFactor );
|
|
|
|
if ( mScaleLocked )
|
|
{
|
|
setMagnificationFactor( mapSettings().magnificationFactor() / scaleFactor );
|
|
}
|
|
else
|
|
{
|
|
// transform the mouse pos to map coordinates
|
|
QgsPointXY center = getCoordinateTransform()->toMapPoint( x, y );
|
|
QgsRectangle r = mapSettings().visibleExtent();
|
|
r.scale( scaleFactor, ¢er );
|
|
setExtent( r, true );
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::setScaleLocked( bool isLocked )
|
|
{
|
|
mScaleLocked = isLocked;
|
|
}
|
|
|
|
void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
|
|
{
|
|
mCanvasProperties->mouseLastXY = e->pos();
|
|
|
|
if ( mCanvasProperties->panSelectorDown )
|
|
{
|
|
panAction( e );
|
|
}
|
|
else if ( mZoomDragging )
|
|
{
|
|
mZoomRect.setBottomRight( e->pos() );
|
|
mZoomRubberBand->setToCanvasRectangle( mZoomRect );
|
|
mZoomRubberBand->show();
|
|
}
|
|
else
|
|
{
|
|
// call handler of current map tool
|
|
if ( mMapTool )
|
|
{
|
|
std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
|
|
mMapTool->canvasMoveEvent( me.get() );
|
|
}
|
|
}
|
|
|
|
// show x y on status bar
|
|
QPoint xy = e->pos();
|
|
QgsPointXY coord = getCoordinateTransform()->toMapCoordinates( xy );
|
|
emit xyCoordinates( coord );
|
|
}
|
|
|
|
void QgsMapCanvas::setMapTool( QgsMapTool *tool )
|
|
{
|
|
if ( !tool )
|
|
return;
|
|
|
|
if ( mMapTool )
|
|
{
|
|
disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
|
|
mMapTool->deactivate();
|
|
}
|
|
|
|
if ( ( tool->flags() & QgsMapTool::Transient )
|
|
&& mMapTool && !( mMapTool->flags() & QgsMapTool::Transient ) )
|
|
{
|
|
// if zoom or pan tool will be active, save old tool
|
|
// to bring it back on right click
|
|
// (but only if it wasn't also zoom or pan tool)
|
|
mLastNonZoomMapTool = mMapTool;
|
|
}
|
|
else
|
|
{
|
|
mLastNonZoomMapTool = nullptr;
|
|
}
|
|
|
|
QgsMapTool *oldTool = mMapTool;
|
|
|
|
// set new map tool and activate it
|
|
mMapTool = tool;
|
|
if ( mMapTool )
|
|
{
|
|
connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
|
|
mMapTool->activate();
|
|
}
|
|
|
|
emit mapToolSet( mMapTool, oldTool );
|
|
} // setMapTool
|
|
|
|
void QgsMapCanvas::unsetMapTool( QgsMapTool *tool )
|
|
{
|
|
if ( mMapTool && mMapTool == tool )
|
|
{
|
|
mMapTool->deactivate();
|
|
mMapTool = nullptr;
|
|
emit mapToolSet( nullptr, mMapTool );
|
|
setCursor( Qt::ArrowCursor );
|
|
}
|
|
|
|
if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
|
|
{
|
|
mLastNonZoomMapTool = nullptr;
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::setCanvasColor( const QColor &color )
|
|
{
|
|
// background of map's pixmap
|
|
mSettings.setBackgroundColor( color );
|
|
|
|
// background of the QGraphicsView
|
|
QBrush bgBrush( color );
|
|
setBackgroundBrush( bgBrush );
|
|
#if 0
|
|
QPalette palette;
|
|
palette.setColor( backgroundRole(), color );
|
|
setPalette( palette );
|
|
#endif
|
|
|
|
// background of QGraphicsScene
|
|
mScene->setBackgroundBrush( bgBrush );
|
|
}
|
|
|
|
QColor QgsMapCanvas::canvasColor() const
|
|
{
|
|
return mScene->backgroundBrush().color();
|
|
}
|
|
|
|
void QgsMapCanvas::setSelectionColor( const QColor &color )
|
|
{
|
|
mSettings.setSelectionColor( color );
|
|
}
|
|
|
|
int QgsMapCanvas::layerCount() const
|
|
{
|
|
return mapSettings().layers().size();
|
|
} // layerCount
|
|
|
|
|
|
QList<QgsMapLayer *> QgsMapCanvas::layers() const
|
|
{
|
|
return mapSettings().layers();
|
|
}
|
|
|
|
|
|
void QgsMapCanvas::layerStateChange()
|
|
{
|
|
// called when a layer has changed visibility setting
|
|
|
|
refresh();
|
|
|
|
} // layerStateChange
|
|
|
|
void QgsMapCanvas::layerCrsChange()
|
|
{
|
|
// called when a layer's CRS has been changed
|
|
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
|
|
QString destAuthId = mSettings.destinationCrs().authid();
|
|
getDatumTransformInfo( layer, layer->crs().authid(), destAuthId );
|
|
|
|
} // layerCrsChange
|
|
|
|
|
|
void QgsMapCanvas::freeze( bool frozen )
|
|
{
|
|
mFrozen = frozen;
|
|
}
|
|
|
|
bool QgsMapCanvas::isFrozen() const
|
|
{
|
|
return mFrozen;
|
|
}
|
|
|
|
|
|
double QgsMapCanvas::mapUnitsPerPixel() const
|
|
{
|
|
return mapSettings().mapUnitsPerPixel();
|
|
} // mapUnitsPerPixel
|
|
|
|
QgsUnitTypes::DistanceUnit QgsMapCanvas::mapUnits() const
|
|
{
|
|
return mapSettings().mapUnits();
|
|
}
|
|
|
|
QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
|
|
{
|
|
return mSettings.layerStyleOverrides();
|
|
}
|
|
|
|
void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
|
|
{
|
|
if ( overrides == mSettings.layerStyleOverrides() )
|
|
return;
|
|
|
|
mSettings.setLayerStyleOverrides( overrides );
|
|
clearCache();
|
|
emit layerStyleOverridesChanged();
|
|
}
|
|
|
|
void QgsMapCanvas::setTheme( const QString &theme )
|
|
{
|
|
if ( mTheme == theme )
|
|
return;
|
|
|
|
clearCache();
|
|
if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
|
|
{
|
|
mTheme.clear();
|
|
mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
|
|
setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
|
|
emit themeChanged( QString() );
|
|
}
|
|
else
|
|
{
|
|
mTheme = theme;
|
|
setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
|
|
emit themeChanged( theme );
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::setRenderFlag( bool flag )
|
|
{
|
|
mRenderFlag = flag;
|
|
|
|
if ( mRenderFlag )
|
|
{
|
|
refresh();
|
|
}
|
|
else
|
|
stopRendering();
|
|
}
|
|
|
|
#if 0
|
|
void QgsMapCanvas::connectNotify( const char *signal )
|
|
{
|
|
Q_UNUSED( signal );
|
|
QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
|
|
} //connectNotify
|
|
#endif
|
|
|
|
void QgsMapCanvas::updateDatumTransformEntries()
|
|
{
|
|
QString destAuthId = mSettings.destinationCrs().authid();
|
|
Q_FOREACH ( QgsMapLayer *layer, mSettings.layers() )
|
|
{
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( vl && vl->geometryType() == QgsWkbTypes::NullGeometry )
|
|
continue;
|
|
|
|
// if there are more options, ask the user which datum transform to use
|
|
if ( !mSettings.datumTransformStore().hasEntryForLayer( layer ) )
|
|
getDatumTransformInfo( layer, layer->crs().authid(), destAuthId );
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::layerRepaintRequested( bool deferred )
|
|
{
|
|
if ( !deferred )
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::autoRefreshTriggered()
|
|
{
|
|
if ( mJob )
|
|
{
|
|
// canvas is currently being redrawn, so we skip this auto refresh
|
|
// otherwise we could get stuck in the situation where an auto refresh is triggered
|
|
// too often to allow the canvas to ever finish rendering
|
|
return;
|
|
}
|
|
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::updateAutoRefreshTimer()
|
|
{
|
|
// min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
|
|
// trigger a map refresh on this minimum interval
|
|
int minAutoRefreshInterval = -1;
|
|
Q_FOREACH ( QgsMapLayer *layer, mSettings.layers() )
|
|
{
|
|
if ( layer->hasAutoRefreshEnabled() && layer->autoRefreshInterval() > 0 )
|
|
minAutoRefreshInterval = minAutoRefreshInterval > 0 ? qMin( layer->autoRefreshInterval(), minAutoRefreshInterval ) : layer->autoRefreshInterval();
|
|
}
|
|
|
|
if ( minAutoRefreshInterval > 0 )
|
|
{
|
|
mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
|
|
mAutoRefreshTimer.start();
|
|
}
|
|
else
|
|
{
|
|
mAutoRefreshTimer.stop();
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::projectThemesChanged()
|
|
{
|
|
if ( mTheme.isEmpty() )
|
|
return;
|
|
|
|
if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
|
|
{
|
|
// theme has been removed - stop following
|
|
setTheme( QString() );
|
|
}
|
|
|
|
}
|
|
|
|
QgsMapTool *QgsMapCanvas::mapTool()
|
|
{
|
|
return mMapTool;
|
|
}
|
|
|
|
void QgsMapCanvas::panActionEnd( QPoint releasePoint )
|
|
{
|
|
// move map image and other items to standard position
|
|
moveCanvasContents( true ); // true means reset
|
|
|
|
// use start and end box points to calculate the extent
|
|
QgsPointXY start = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
|
|
QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
|
|
|
|
// modify the center
|
|
double dx = end.x() - start.x();
|
|
double dy = end.y() - start.y();
|
|
QgsPointXY c = center();
|
|
c.set( c.x() - dx, c.y() - dy );
|
|
setCenter( c );
|
|
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::panAction( QMouseEvent *e )
|
|
{
|
|
Q_UNUSED( e );
|
|
|
|
// move all map canvas items
|
|
moveCanvasContents();
|
|
}
|
|
|
|
void QgsMapCanvas::moveCanvasContents( bool reset )
|
|
{
|
|
QPoint pnt( 0, 0 );
|
|
if ( !reset )
|
|
pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
|
|
|
|
setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
|
|
}
|
|
|
|
QPoint QgsMapCanvas::mouseLastXY()
|
|
{
|
|
return mCanvasProperties->mouseLastXY;
|
|
}
|
|
|
|
void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
|
|
{
|
|
if ( !mPreviewEffect )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mPreviewEffect->setEnabled( previewEnabled );
|
|
}
|
|
|
|
bool QgsMapCanvas::previewModeEnabled() const
|
|
{
|
|
if ( !mPreviewEffect )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return mPreviewEffect->isEnabled();
|
|
}
|
|
|
|
void QgsMapCanvas::setPreviewMode( QgsPreviewEffect::PreviewMode mode )
|
|
{
|
|
if ( !mPreviewEffect )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mPreviewEffect->setMode( mode );
|
|
}
|
|
|
|
QgsPreviewEffect::PreviewMode QgsMapCanvas::previewMode() const
|
|
{
|
|
if ( !mPreviewEffect )
|
|
{
|
|
return QgsPreviewEffect::PreviewGrayscale;
|
|
}
|
|
|
|
return mPreviewEffect->mode();
|
|
}
|
|
|
|
QgsSnappingUtils *QgsMapCanvas::snappingUtils() const
|
|
{
|
|
if ( !mSnappingUtils )
|
|
{
|
|
// associate a dummy instance, but better than null pointer
|
|
QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
|
|
c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
|
|
}
|
|
return mSnappingUtils;
|
|
}
|
|
|
|
void QgsMapCanvas::setSnappingUtils( QgsSnappingUtils *utils )
|
|
{
|
|
mSnappingUtils = utils;
|
|
}
|
|
|
|
void QgsMapCanvas::readProject( const QDomDocument &doc )
|
|
{
|
|
QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
|
|
if ( nodes.count() )
|
|
{
|
|
QDomNode node = nodes.item( 0 );
|
|
|
|
// Search the specific MapCanvas node using the name
|
|
if ( nodes.count() > 1 )
|
|
{
|
|
for ( int i = 0; i < nodes.size(); ++i )
|
|
{
|
|
QDomElement elementNode = nodes.at( i ).toElement();
|
|
|
|
if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
|
|
{
|
|
node = nodes.at( i );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsMapSettings tmpSettings;
|
|
tmpSettings.readXml( node );
|
|
if ( objectName() != QStringLiteral( "theMapCanvas" ) )
|
|
{
|
|
// never manually set the crs for the main canvas - this is instead connected to the project CRS
|
|
setDestinationCrs( tmpSettings.destinationCrs() );
|
|
}
|
|
setExtent( tmpSettings.extent() );
|
|
setRotation( tmpSettings.rotation() );
|
|
mSettings.datumTransformStore() = tmpSettings.datumTransformStore();
|
|
enableMapTileRendering( tmpSettings.testFlag( QgsMapSettings::RenderMapTile ) );
|
|
|
|
clearExtentHistory(); // clear the extent history on project load
|
|
|
|
QDomElement elem = node.toElement();
|
|
if ( elem.hasAttribute( QStringLiteral( "theme" ) ) )
|
|
{
|
|
if ( QgsProject::instance()->mapThemeCollection()->hasMapTheme( elem.attribute( QStringLiteral( "theme" ) ) ) )
|
|
{
|
|
setTheme( elem.attribute( QStringLiteral( "theme" ) ) );
|
|
}
|
|
}
|
|
setAnnotationsVisible( elem.attribute( QStringLiteral( "annotationsVisible" ), QStringLiteral( "1" ) ).toInt() );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( "Couldn't read mapcanvas information from project" );
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::writeProject( QDomDocument &doc )
|
|
{
|
|
// create node "mapcanvas" and call mMapRenderer->writeXml()
|
|
|
|
QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
|
|
if ( !nl.count() )
|
|
{
|
|
QgsDebugMsg( "Unable to find qgis element in project file" );
|
|
return;
|
|
}
|
|
QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
|
|
|
|
QDomElement mapcanvasNode = doc.createElement( QStringLiteral( "mapcanvas" ) );
|
|
mapcanvasNode.setAttribute( QStringLiteral( "name" ), objectName() );
|
|
if ( !mTheme.isEmpty() )
|
|
mapcanvasNode.setAttribute( QStringLiteral( "theme" ), mTheme );
|
|
mapcanvasNode.setAttribute( QStringLiteral( "annotationsVisible" ), mAnnotationsVisible );
|
|
qgisNode.appendChild( mapcanvasNode );
|
|
|
|
mSettings.writeXml( mapcanvasNode, doc );
|
|
// TODO: store only units, extent, projections, dest CRS
|
|
}
|
|
|
|
void QgsMapCanvas::getDatumTransformInfo( const QgsMapLayer *ml, const QString &srcAuthId, const QString &destAuthId )
|
|
{
|
|
if ( !ml )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//check if default datum transformation available
|
|
QgsSettings s;
|
|
QString settingsString = "/Projections/" + srcAuthId + "//" + destAuthId;
|
|
QVariant defaultSrcTransform = s.value( settingsString + "_srcTransform" );
|
|
QVariant defaultDestTransform = s.value( settingsString + "_destTransform" );
|
|
if ( defaultSrcTransform.isValid() && defaultDestTransform.isValid() )
|
|
{
|
|
mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, defaultSrcTransform.toInt(), defaultDestTransform.toInt() );
|
|
return;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem srcCRS = QgsCoordinateReferenceSystem::fromOgcWmsCrs( srcAuthId );
|
|
QgsCoordinateReferenceSystem destCRS = QgsCoordinateReferenceSystem::fromOgcWmsCrs( destAuthId );
|
|
|
|
if ( !s.value( QStringLiteral( "/Projections/showDatumTransformDialog" ), false ).toBool() )
|
|
{
|
|
// just use the default transform
|
|
mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, -1, -1 );
|
|
return;
|
|
}
|
|
|
|
//get list of datum transforms
|
|
QList< QList< int > > dt = QgsCoordinateTransform::datumTransformations( srcCRS, destCRS );
|
|
if ( dt.size() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//if several possibilities: present dialog
|
|
QgsDatumTransformDialog d( ml->name(), dt );
|
|
d.setDatumTransformInfo( srcCRS.authid(), destCRS.authid() );
|
|
if ( d.exec() == QDialog::Accepted )
|
|
{
|
|
int srcTransform = -1;
|
|
int destTransform = -1;
|
|
QList<int> t = d.selectedDatumTransform();
|
|
if ( !t.isEmpty() )
|
|
{
|
|
srcTransform = t.at( 0 );
|
|
}
|
|
if ( t.size() > 1 )
|
|
{
|
|
destTransform = t.at( 1 );
|
|
}
|
|
mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, srcTransform, destTransform );
|
|
if ( d.rememberSelection() )
|
|
{
|
|
s.setValue( settingsString + "_srcTransform", srcTransform );
|
|
s.setValue( settingsString + "_destTransform", destTransform );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, -1, -1 );
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPointXY *center )
|
|
{
|
|
if ( mScaleLocked )
|
|
{
|
|
// zoom map to mouse cursor by magnifying
|
|
setMagnificationFactor( mapSettings().magnificationFactor() / scaleFactor );
|
|
}
|
|
else
|
|
{
|
|
QgsRectangle r = mapSettings().extent();
|
|
r.scale( scaleFactor, center );
|
|
setExtent( r, true );
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::selectionChangedSlot()
|
|
{
|
|
// Find out which layer it was that sent the signal.
|
|
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
|
|
emit selectionChanged( layer );
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *e )
|
|
{
|
|
// By default graphics view delegates the drag events to graphics items.
|
|
// But we do not want that and by ignoring the drag enter we let the
|
|
// parent (e.g. QgisApp) to handle drops of map layers etc.
|
|
e->ignore();
|
|
}
|
|
|
|
void QgsMapCanvas::mapToolDestroyed()
|
|
{
|
|
QgsDebugMsg( "maptool destroyed" );
|
|
mMapTool = nullptr;
|
|
}
|
|
|
|
bool QgsMapCanvas::event( QEvent *e )
|
|
{
|
|
if ( !QTouchDevice::devices().empty() )
|
|
{
|
|
if ( e->type() == QEvent::Gesture )
|
|
{
|
|
// call handler of current map tool
|
|
if ( mMapTool )
|
|
{
|
|
return mMapTool->gestureEvent( static_cast<QGestureEvent *>( e ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// pass other events to base class
|
|
return QGraphicsView::event( e );
|
|
}
|
|
|
|
void QgsMapCanvas::refreshAllLayers()
|
|
{
|
|
// reload all layers in canvas
|
|
for ( int i = 0; i < layerCount(); i++ )
|
|
{
|
|
QgsMapLayer *l = layer( i );
|
|
if ( l )
|
|
l->reload();
|
|
}
|
|
|
|
// clear the cache
|
|
clearCache();
|
|
|
|
// and then refresh
|
|
refresh();
|
|
}
|
|
|
|
void QgsMapCanvas::waitWhileRendering()
|
|
{
|
|
while ( mRefreshScheduled || mJob )
|
|
{
|
|
QgsApplication::processEvents();
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::setSegmentationTolerance( double tolerance )
|
|
{
|
|
mSettings.setSegmentationTolerance( tolerance );
|
|
}
|
|
|
|
void QgsMapCanvas::setSegmentationToleranceType( QgsAbstractGeometry::SegmentationToleranceType type )
|
|
{
|
|
mSettings.setSegmentationToleranceType( type );
|
|
}
|
|
|
|
QList<QgsMapCanvasAnnotationItem *> QgsMapCanvas::annotationItems() const
|
|
{
|
|
QList<QgsMapCanvasAnnotationItem *> annotationItemList;
|
|
QList<QGraphicsItem *> itemList = mScene->items();
|
|
QList<QGraphicsItem *>::iterator it = itemList.begin();
|
|
for ( ; it != itemList.end(); ++it )
|
|
{
|
|
QgsMapCanvasAnnotationItem *aItem = dynamic_cast< QgsMapCanvasAnnotationItem *>( *it );
|
|
if ( aItem )
|
|
{
|
|
annotationItemList.push_back( aItem );
|
|
}
|
|
}
|
|
|
|
return annotationItemList;
|
|
}
|
|
|
|
void QgsMapCanvas::setAnnotationsVisible( bool show )
|
|
{
|
|
mAnnotationsVisible = show;
|
|
Q_FOREACH ( QgsMapCanvasAnnotationItem *item, annotationItems() )
|
|
{
|
|
item->setVisible( show );
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::setLabelingEngineSettings( const QgsLabelingEngineSettings &settings )
|
|
{
|
|
mSettings.setLabelingEngineSettings( settings );
|
|
}
|
|
|
|
const QgsLabelingEngineSettings &QgsMapCanvas::labelingEngineSettings() const
|
|
{
|
|
return mSettings.labelingEngineSettings();
|
|
}
|
|
|
|
void QgsMapCanvas::startPreviewJobs()
|
|
{
|
|
stopPreviewJobs(); //just in case still running
|
|
|
|
QgsRectangle mapRect = mSettings.visibleExtent();
|
|
|
|
for ( int j = 0; j < 3; ++j )
|
|
{
|
|
for ( int i = 0; i < 3; ++i )
|
|
{
|
|
if ( i == 1 && j == 1 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
//copy settings, only update extent
|
|
QgsMapSettings jobSettings = mSettings;
|
|
|
|
double dx = ( i - 1 ) * mapRect.width();
|
|
double dy = ( 1 - j ) * mapRect.height();
|
|
QgsRectangle jobExtent = mapRect;
|
|
jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
|
|
jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
|
|
jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
|
|
jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
|
|
|
|
jobSettings.setExtent( jobExtent );
|
|
|
|
QgsMapRendererQImageJob *job = new QgsMapRendererParallelJob( jobSettings );
|
|
mPreviewJobs.append( job );
|
|
connect( job, SIGNAL( finished() ), this, SLOT( previewJobFinished() ) );
|
|
job->start();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsMapCanvas::stopPreviewJobs()
|
|
{
|
|
QList< QgsMapRendererQImageJob * >::iterator it = mPreviewJobs.begin();
|
|
for ( ; it != mPreviewJobs.end(); ++it )
|
|
{
|
|
if ( *it )
|
|
{
|
|
( *it )->cancel();
|
|
}
|
|
delete ( *it );
|
|
}
|
|
mPreviewJobs.clear();
|
|
}
|