[FEATURE] Legend filtering based on map content (in main window, composer, WMS)

There is new "filter" button in layers panel that toggles this functionality
and in composer legend widget.

Related feature is that layer tree now shows symbols in map units with correct size
(even when filtering is not enabled) so as the map view changes the legend node icons
are updated too (if they use map units).

GetLegendGraphics in WMS server
-------------------------------

This is an extension of standard GetLegendGraphics request according to MapServer RFC 101.
See the document for more details: http://mapserver.org/development/rfc/ms-rfc-101.html

In summary, clients need to add BBOX and CRS/SRS parameters to get appropriate legend based on the given map view.
Parameters WIDTH and HEIGHT are also taken into account as they specify map view image size for correct calculation
of size of legend symbols (if they are based on map units).

--

This software has been commissioned by Tuscany Region (Italy),
co-funded by the European Commission and developed under the project LIFE12 ENV/IT/001054 LIFE + IMAGINE.
The software has been realized by Gis3W s.a.s.

Questo software è stato commissionato da Regione Toscana (Italia),
cofinanziato dalla Commissione Europea e sviluppato nell'ambito del progetto LIFE12 ENV/IT/001054 LIFE + IMAGINE.
Il software è stato realizzato da Gis3W s.a.s.
This commit is contained in:
Martin Dobias 2014-09-25 12:00:45 +02:00
parent a111c85c06
commit e37a5ad8df
48 changed files with 896 additions and 127 deletions

View File

@ -58,6 +58,13 @@ class QgsComposerLegend : QgsComposerItem
//! @note added in 2.6
bool autoUpdateModel() const;
//! Set whether legend items should be filtered to show just the ones visible in the associated map
//! @note added in 2.6
void setLegendFilterByMapEnabled( bool enabled );
//! Find out whether legend items are filtered to show just the ones visible in the associated map
//! @note added in 2.6
bool legendFilterByMapEnabled() const;
//setters and getters
void setTitle( const QString& t );
QString title() const;

View File

@ -111,6 +111,10 @@ class QgsComposerMap : QgsComposerItem
/** \brief Create cache image */
void cache();
/** Return map settings that would be used for drawing of the map
* @note added in 2.6 */
QgsMapSettings mapSettings( const QgsRectangle& extent, const QSizeF& size, int dpi ) const;
/** \brief Get identification number*/
int id() const;
@ -737,6 +741,10 @@ class QgsComposerMap : QgsComposerItem
void connectMapOverviewSignals() /Deprecated/;
/**Calculates the extent to request and the yShift of the top-left point in case of rotation.
* @note added in 2.6 */
void requestedExtent( QgsRectangle& extent ) const;
signals:
void extentChanged();

View File

@ -85,7 +85,8 @@ class QgsLayerTreeModel : QAbstractItemModel
//! Return legend node for given index. Returns null for invalid index
//! @note added in 2.6
static QgsLayerTreeModelLegendNode* index2legendNode( const QModelIndex& index );
//! Return index for a given legend node. If the legend node does not belong to the layer tree, the result is undefined
//! Return index for a given legend node. If the legend node does not belong to the layer tree, the result is undefined.
//! If the legend node is belongs to the tree but it is filtered out, invalid model index is returned.
//! @note added in 2.6
QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode );
@ -124,6 +125,22 @@ class QgsLayerTreeModel : QAbstractItemModel
void setLegendFilterByScale( double scaleDenominator );
double legendFilterByScale() const;
//! Force only display of legend nodes which are valid for given map settings.
//! Setting null pointer or invalid map settings will disable the functionality.
//! Ownership of map settings pointer does not change.
//! @note added in 2.6
void setLegendFilterByMap( const QgsMapSettings* settings );
const QgsMapSettings* legendFilterByMap() const;
//! Give the layer tree model hints about the currently associated map view
//! so that legend nodes that use map units can be scaled currectly
//! @note added in 2.6
void setLegendMapViewData( double mapUnitsPerPixel, int dpi, double scale );
//! Get hints about map view - to be used in legend nodes. Arguments that are not null will receive values.
//! If there are no valid map view data (from previous call to setLegendMapViewData()), returned values are zeros.
//! @note added in 2.6
void legendMapViewData( double *mapUnitsPerPixel /Out/, int *dpi /Out/, double *scale /Out/ );
//! Return true if index represents a legend node (instead of layer node)
//! @deprecated use index2legendNode()
bool isIndexSymbologyNode( const QModelIndex& index ) const /Deprecated/;

View File

@ -19,11 +19,15 @@ class QgsLayerTreeModelLegendNode : QObject
enum LegendNodeRoles
{
RuleKeyRole
RuleKeyRole, //!< rule key of the node (QString)
SymbolV2LegacyRuleKeyRole //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
};
/** Return pointer to the parent layer node */
QgsLayerTreeLayer* parent() const;
QgsLayerTreeLayer* layerNode() const;
/** Return pointer to model owning this legend node */
QgsLayerTreeModel* model() const;
/** Return item flags associated with the item. Default implementation returns Qt::ItemIsEnabled. */
virtual Qt::ItemFlags flags() const;
@ -42,6 +46,10 @@ class QgsLayerTreeModelLegendNode : QObject
virtual bool isScaleOK( double scale ) const;
/** Notification from model that information from associated map view has changed.
* Default implementation does nothing. */
virtual void invalidateMapBasedData();
struct ItemContext
{
//! Painter
@ -109,7 +117,7 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
#include <qgslayertreemodellegendnode.h>
%End
public:
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item );
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item, QObject* parent /TransferThis/ = 0 );
~QgsSymbolV2LegendNode();
virtual Qt::ItemFlags flags() const;
@ -123,6 +131,8 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
void setUserLabel( const QString& userLabel );
virtual bool isScaleOK( double scale ) const;
virtual void invalidateMapBasedData();
};

View File

@ -287,6 +287,14 @@ class QgsMapRenderer : QObject
//! @note added in 2.4
const QgsMapSettings& mapSettings();
/** Convenience function to project an extent into the layer source
* CRS, but also split it into two extents if it crosses
* the +/- 180 degree line. Modifies the given extent to be in the
* source CRS coordinates, and if it was split, returns true, and
* also sets the contents of the r2 parameter
*/
bool splitLayersExtent( QgsMapLayer* layer, QgsRectangle& extent /In,Out/, QgsRectangle& r2 /Out/ );
signals:
//! @deprecated in 2.4 - not emitted anymore
@ -331,11 +339,4 @@ class QgsMapRenderer : QObject
//! adjust extent to fit the pixmap size
void adjustExtentToSize();
/** Convenience function to project an extent into the layer source
* CRS, but also split it into two extents if it crosses
* the +/- 180 degree line. Modifies the given extent to be in the
* source CRS coordinates, and if it was split, returns true, and
* also sets the contents of the r2 parameter
*/
bool splitLayersExtent( QgsMapLayer* layer, QgsRectangle& extent, QgsRectangle& r2 );
};

View File

@ -47,6 +47,8 @@ class QgsCategorizedSymbolRendererV2 : QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context );

View File

@ -51,6 +51,8 @@ class QgsGraduatedSymbolRendererV2 : QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context );

View File

@ -45,8 +45,12 @@ class QgsInvertedPolygonRenderer : QgsFeatureRendererV2
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsLegendSymbologyList legendSymbologyItems( QSize iconSize );
/** Proxy that will call this method on the embedded renderer.
@note not available in python bindings

View File

@ -60,6 +60,14 @@ class QgsFeatureRendererV2
*/
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature ) = 0;
/**
* Return symbol for feature. The difference compared to symbolForFeature() is that it returns original
* symbol which can be used as an identifier for renderer's rule - the former may return a temporary replacement
* of a symbol for use in rendering.
* @note added in 2.6
*/
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields ) = 0;
//! @deprecated since 2.4 - not using QgsVectorLayer directly anymore
@ -175,6 +183,11 @@ class QgsFeatureRendererV2
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
//! Equivalent of originalSymbolsForFeature() call
//! extended to support renderers that may use more symbols per feature - similar to symbolsForFeature()
//! @note added in 2.6
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
protected:
QgsFeatureRendererV2( QString type );

View File

@ -216,6 +216,8 @@ class QgsRuleBasedRendererV2 : QgsFeatureRendererV2
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
//! returns bitwise OR-ed capabilities of the renderer
//! \note added in 2.0
virtual int capabilities();

View File

@ -11,6 +11,8 @@ class QgsSingleSymbolRendererV2 : QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context );

View File

@ -68,7 +68,8 @@ class QgsSymbolLayerV2Utils
static void drawStippledBackround( QPainter* painter, QRect rect );
static QPixmap symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size );
//! @note customContext parameter added in 2.6
static QPixmap symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size, QgsRenderContext* customContext = 0 );
static QPixmap colorRampPreviewPixmap( QgsVectorColorRampV2* ramp, QSize size );
/**Returns the maximum estimated bleed for the symbol */

View File

@ -145,6 +145,7 @@ void QgsComposerLegendWidget::setGuiElements()
blockAllSignals( true );
mTitleLineEdit->setText( mLegend->title() );
mTitleAlignCombo->setCurrentIndex( alignment );
mFilterByMapToolButton->setChecked( mLegend->legendFilterByMapEnabled() );
mColumnCountSpinBox->setValue( mLegend->columnCount() );
mSplitLayerCheckBox->setChecked( mLegend->splitLayer() );
mEqualColumnWidthCheckBox->setChecked( mLegend->equalColumnWidth() );
@ -531,8 +532,8 @@ void QgsComposerLegendWidget::on_mMoveDownToolButton_clicked()
}
else // legend node
{
_moveLegendNode( legendNode->parent(), index.row(), 1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->parent() );
_moveLegendNode( legendNode->layerNode(), index.row(), 1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
}
mItemTreeView->setCurrentIndex( mItemTreeView->layerTreeModel()->index( index.row() + 1, 0, parentIndex ) );
@ -568,8 +569,8 @@ void QgsComposerLegendWidget::on_mMoveUpToolButton_clicked()
}
else // legend node
{
_moveLegendNode( legendNode->parent(), index.row(), -1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->parent() );
_moveLegendNode( legendNode->layerNode(), index.row(), -1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
}
mItemTreeView->setCurrentIndex( mItemTreeView->layerTreeModel()->index( index.row() - 1, 0, parentIndex ) );
@ -689,7 +690,7 @@ void QgsComposerLegendWidget::on_mRemoveToolButton_clicked()
{
if ( QgsLayerTreeModelLegendNode* legendNode = mItemTreeView->layerTreeModel()->index2legendNode( index ) )
{
QgsLayerTreeLayer* nodeLayer = legendNode->parent();
QgsLayerTreeLayer* nodeLayer = legendNode->layerNode();
nodesWithRemoval[nodeLayer].append( index.row() );
}
}
@ -773,10 +774,10 @@ void QgsComposerLegendWidget::on_mEditPushButton_clicked()
}
else if ( legendNode )
{
QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->parent() );
QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->layerNode() );
int originalIndex = ( idx.row() >= 0 && idx.row() < order.count() ? order[idx.row()] : -1 );
QgsMapLayerLegendUtils::setLegendNodeUserLabel( legendNode->parent(), originalIndex, newText );
model->refreshLayerLegend( legendNode->parent() );
QgsMapLayerLegendUtils::setLegendNodeUserLabel( legendNode->layerNode(), originalIndex, newText );
model->refreshLayerLegend( legendNode->layerNode() );
}
mLegend->adjustBoxSize();
@ -806,7 +807,7 @@ void QgsComposerLegendWidget::resetLayerNodeToDefaults()
}
if ( QgsLayerTreeModelLegendNode* legendNode = mItemTreeView->layerTreeModel()->index2legendNode( currentIndex ) )
{
nodeLayer = legendNode->parent();
nodeLayer = legendNode->layerNode();
}
if ( !nodeLayer )
@ -853,6 +854,15 @@ void QgsComposerLegendWidget::on_mCountToolButton_clicked( bool checked )
mLegend->endCommand();
}
void QgsComposerLegendWidget::on_mFilterByMapToolButton_clicked( bool checked )
{
mLegend->beginCommand( tr( "Legend updated" ) );
mLegend->setLegendFilterByMapEnabled( checked );
mLegend->update();
mLegend->adjustBoxSize();
mLegend->endCommand();
}
void QgsComposerLegendWidget::on_mUpdateAllPushButton_clicked()
{
updateLegend();
@ -891,6 +901,7 @@ void QgsComposerLegendWidget::blockAllSignals( bool b )
mItemTreeView->blockSignals( b );
mCheckBoxAutoUpdate->blockSignals( b );
mMapComboBox->blockSignals( b );
mFilterByMapToolButton->blockSignals( b );
mColumnCountSpinBox->blockSignals( b );
mSplitLayerCheckBox->blockSignals( b );
mEqualColumnWidthCheckBox->blockSignals( b );

View File

@ -76,6 +76,7 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
void on_mAddToolButton_clicked();
void on_mEditPushButton_clicked();
void on_mCountToolButton_clicked( bool checked );
void on_mFilterByMapToolButton_clicked( bool checked );
void resetLayerNodeToDefaults();
void on_mUpdateAllPushButton_clicked();
void on_mAddGroupToolButton_clicked();

View File

@ -2333,6 +2333,14 @@ void QgisApp::initLayerTreeView()
btnVisibilityPresets->setPopupMode( QToolButton::InstantPopup );
btnVisibilityPresets->setMenu( QgsVisibilityPresets::instance()->menu() );
// filter legend tool button
mBtnFilterLegend = new QToolButton;
mBtnFilterLegend->setAutoRaise( true );
mBtnFilterLegend->setCheckable( true );
mBtnFilterLegend->setToolTip( tr( "Filter Legend By Map Content" ) );
mBtnFilterLegend->setIcon( QgsApplication::getThemeIcon( "/mActionFilter.png" ) );
connect( mBtnFilterLegend, SIGNAL( clicked() ), this, SLOT( toggleFilterLegendByMap() ) );
// expand / collapse tool buttons
QToolButton* btnExpandAll = new QToolButton;
btnExpandAll->setAutoRaise( true );
@ -2353,6 +2361,7 @@ void QgisApp::initLayerTreeView()
toolbarLayout->setContentsMargins( QMargins( 5, 0, 5, 0 ) );
toolbarLayout->addWidget( btnAddGroup );
toolbarLayout->addWidget( btnVisibilityPresets );
toolbarLayout->addWidget( mBtnFilterLegend );
toolbarLayout->addWidget( btnExpandAll );
toolbarLayout->addWidget( btnCollapseAll );
toolbarLayout->addWidget( btnRemoveItem );
@ -3817,6 +3826,8 @@ bool QgisApp::addProject( QString projectFile )
mMapCanvas->updateScale();
QgsDebugMsg( "Scale restored..." );
setFilterLegendByMapEnabled( QgsProject::instance()->readBoolEntry( "Legend", "filterByMap" ) );
QSettings settings;
// does the project have any macros?
@ -4213,6 +4224,38 @@ void QgisApp::activateDeuteranopePreview()
mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewDeuteranope );
}
void QgisApp::toggleFilterLegendByMap()
{
bool enabled = layerTreeView()->layerTreeModel()->legendFilterByMap();
setFilterLegendByMapEnabled( !enabled );
}
void QgisApp::setFilterLegendByMapEnabled( bool enabled )
{
QgsLayerTreeModel* model = layerTreeView()->layerTreeModel();
bool wasEnabled = model->legendFilterByMap();
if ( wasEnabled == enabled )
return; // no change
mBtnFilterLegend->setChecked( enabled );
if ( enabled )
{
connect( mMapCanvas, SIGNAL( mapCanvasRefreshed() ), this, SLOT( updateFilterLegendByMap() ) );
model->setLegendFilterByMap( &mMapCanvas->mapSettings() );
}
else
{
disconnect( mMapCanvas, SIGNAL( mapCanvasRefreshed() ), this, SLOT( updateFilterLegendByMap() ) );
model->setLegendFilterByMap( 0 );
}
}
void QgisApp::updateFilterLegendByMap()
{
layerTreeView()->layerTreeModel()->setLegendFilterByMap( &mMapCanvas->mapSettings() );
}
void QgisApp::saveMapAsImage()
{
QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
@ -7853,6 +7896,8 @@ void QgisApp::closeProject()
mTrustedMacros = false;
setFilterLegendByMapEnabled( false );
deletePrintComposers();
removeAnnotationItems();
// clear out any stuff from project
@ -8595,6 +8640,9 @@ void QgisApp::layersWereAdded( QList<QgsMapLayer *> theLayers )
void QgisApp::showExtents()
{
// allow symbols in the legend update their preview if they use map units
mLayerTreeView->layerTreeModel()->setLegendMapViewData( mMapCanvas->mapUnitsPerPixel(), mMapCanvas->mapSettings().outputDpi(), mMapCanvas->scale() );
if ( !mToggleExtentsViewButton->isChecked() )
{
return;
@ -9710,6 +9758,8 @@ void QgisApp::writeProject( QDomDocument &doc )
delete clonedRoot;
doc.firstChildElement( "qgis" ).appendChild( oldLegendElem );
QgsProject::instance()->writeEntry( "Legend", "filterByMap", (bool) layerTreeView()->layerTreeModel()->legendFilterByMap() );
projectChanged( doc );
}

View File

@ -1231,6 +1231,10 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
* @note added in 2.3 */
void activateDeuteranopePreview();
void toggleFilterLegendByMap();
void updateFilterLegendByMap();
void setFilterLegendByMapEnabled( bool enabled );
/** Make the user feel dizzy */
void dizzy();
@ -1617,6 +1621,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QWidget *mMacrosWarn;
QgsVectorLayerTools* mVectorLayerTools;
QToolButton* mBtnFilterLegend;
#ifdef HAVE_TOUCH
bool gestureEvent( QGestureEvent *event );
void tapAndHoldTriggered( QTapAndHoldGesture *gesture );

View File

@ -101,6 +101,7 @@ SET(QGIS_CORE_SRCS
qgslegendrenderer.cpp
qgslegendsettings.cpp
qgslogger.cpp
qgsmaphittest.cpp
qgsmaplayer.cpp
qgsmaplayerlegend.cpp
qgsmaplayerregistry.cpp
@ -497,6 +498,7 @@ SET(QGIS_CORE_HDRS
qgslegendrenderer.h
qgslegendsettings.h
qgslogger.h
qgsmaphittest.h
qgsmaplayer.h
qgsmaplayerlegend.h
qgsmaplayerregistry.h

View File

@ -35,6 +35,7 @@ QgsComposerLegend::QgsComposerLegend( QgsComposition* composition )
: QgsComposerItem( composition )
, mCustomLayerTree( 0 )
, mComposerMap( 0 )
, mLegendFilterByMap( false )
{
mLegendModel2 = new QgsLegendModelV2( QgsProject::instance()->layerTreeRoot() );
@ -143,6 +144,7 @@ void QgsComposerLegend::setAutoUpdateModel( bool autoUpdate )
return;
setCustomLayerTree( autoUpdate ? 0 : QgsLayerTree::toGroup( QgsProject::instance()->layerTreeRoot()->clone() ) );
adjustBoxSize();
}
bool QgsComposerLegend::autoUpdateModel() const
@ -150,6 +152,12 @@ bool QgsComposerLegend::autoUpdateModel() const
return !mCustomLayerTree;
}
void QgsComposerLegend::setLegendFilterByMapEnabled( bool enabled )
{
mLegendFilterByMap = enabled;
updateFilterByMap();
}
void QgsComposerLegend::setTitle( const QString& t )
{
mSettings.setTitle( t );
@ -271,6 +279,9 @@ bool QgsComposerLegend::writeXML( QDomElement& elem, QDomDocument & doc ) const
mCustomLayerTree->writeXML( composerLegendElem );
}
if ( mLegendFilterByMap )
composerLegendElem.setAttribute( "legendFilterByMap", "1" );
return _writeXML( composerLegendElem, doc );
}
@ -345,6 +356,8 @@ bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument
_readXML( composerItemElem, doc );
}
mLegendFilterByMap = itemElem.attribute( "legendFilterByMap", "0" ).toInt();
// < 2.0 projects backward compatibility >>>>>
//title font
QString titleFontString = itemElem.attribute( "titleFont" );
@ -420,7 +433,11 @@ void QgsComposerLegend::setComposerMap( const QgsComposerMap* map )
if ( map )
{
QObject::connect( map, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
QObject::connect( map, SIGNAL( itemChanged() ), this, SLOT( updateFilterByMap() ) );
QObject::connect( map, SIGNAL( extentChanged() ), this, SLOT( updateFilterByMap() ) );
}
updateFilterByMap();
}
void QgsComposerLegend::invalidateCurrentMap()
@ -428,10 +445,35 @@ void QgsComposerLegend::invalidateCurrentMap()
if ( mComposerMap )
{
disconnect( mComposerMap, SIGNAL( destroyed( QObject* ) ), this, SLOT( invalidateCurrentMap() ) );
disconnect( mComposerMap, SIGNAL( itemChanged() ), this, SLOT( updateFilterByMap() ) );
disconnect( mComposerMap, SIGNAL( extentChanged() ), this, SLOT( updateFilterByMap() ) );
}
mComposerMap = 0;
}
void QgsComposerLegend::updateFilterByMap()
{
if ( mComposerMap && mLegendFilterByMap )
{
int dpi = mComposition->printResolution();
QgsRectangle requestRectangle;
mComposerMap->requestedExtent( requestRectangle );
QSizeF theSize( requestRectangle.width(), requestRectangle.height() );
theSize *= mComposerMap->mapUnitsToMM() * dpi / 25.4;
QgsMapSettings ms = mComposerMap->mapSettings( requestRectangle, theSize, dpi );
mLegendModel2->setLegendFilterByMap( &ms );
}
else
mLegendModel2->setLegendFilterByMap( 0 );
adjustBoxSize();
update();
}
// -------------------------------------------------------------------------
#include "qgslayertreemodellegendnode.h"
#include "qgsvectorlayer.h"

View File

@ -85,6 +85,13 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
//! @note added in 2.6
bool autoUpdateModel() const;
//! Set whether legend items should be filtered to show just the ones visible in the associated map
//! @note added in 2.6
void setLegendFilterByMapEnabled( bool enabled );
//! Find out whether legend items are filtered to show just the ones visible in the associated map
//! @note added in 2.6
bool legendFilterByMapEnabled() const { return mLegendFilterByMap; }
//setters and getters
void setTitle( const QString& t );
QString title() const;
@ -176,6 +183,9 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
/**Sets mCompositionMap to 0 if the map is deleted*/
void invalidateCurrentMap();
private slots:
void updateFilterByMap();
private:
QgsComposerLegend(); //forbidden
@ -190,6 +200,8 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
QgsLegendSettings mSettings;
const QgsComposerMap* mComposerMap;
bool mLegendFilterByMap;
};
#endif

View File

@ -176,6 +176,17 @@ void QgsComposerMap::draw( QPainter *painter, const QgsRectangle& extent, const
return;
}
// render
QgsMapRendererCustomPainterJob job( mapSettings( extent, size, dpi ), painter );
// Render the map in this thread. This is done because of problems
// with printing to printer on Windows (printing to PDF is fine though).
// Raster images were not displayed - see #10599
job.renderSynchronously();
}
QgsMapSettings QgsComposerMap::mapSettings( const QgsRectangle& extent, const QSizeF& size, int dpi ) const
{
const QgsMapSettings& ms = mComposition->mapSettings();
QgsMapSettings jobMapSettings;
@ -218,12 +229,7 @@ void QgsComposerMap::draw( QPainter *painter, const QgsRectangle& extent, const
jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
jobMapSettings.setFlag( QgsMapSettings::UseAdvancedEffects, mComposition->useAdvancedEffects() ); // respect the composition's useAdvancedEffects flag
// render
QgsMapRendererCustomPainterJob job( jobMapSettings, painter );
// Render the map in this thread. This is done because of problems
// with printing to printer on Windows (printing to PDF is fine though).
// Raster images were not displayed - see #10599
job.renderSynchronously();
return jobMapSettings;
}
void QgsComposerMap::cache( void )
@ -1964,7 +1970,7 @@ QString QgsComposerMap::displayName() const
return tr( "Map %1" ).arg( mId );
}
void QgsComposerMap::requestedExtent( QgsRectangle& extent )
void QgsComposerMap::requestedExtent( QgsRectangle& extent ) const
{
QgsRectangle newExtent = *currentMapExtent();
if ( mEvaluatedMapRotation == 0 )

View File

@ -151,6 +151,10 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
/** \brief Create cache image */
void cache();
/** Return map settings that would be used for drawing of the map
* @note added in 2.6 */
QgsMapSettings mapSettings( const QgsRectangle& extent, const QSizeF& size, int dpi ) const;
/** \brief Get identification number*/
int id() const {return mId;}
@ -774,6 +778,10 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
Q_DECL_DEPRECATED void connectMapOverviewSignals();
/**Calculates the extent to request and the yShift of the top-left point in case of rotation.
* @note added in 2.6 */
void requestedExtent( QgsRectangle& extent ) const;
signals:
void extentChanged();
@ -893,8 +901,6 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
/** mapPolygon variant using a given extent */
void mapPolygon( const QgsRectangle& extent, QPolygonF& poly ) const;
/**Calculates the extent to request and the yShift of the top-left point in case of rotation.*/
void requestedExtent( QgsRectangle& extent );
/**Scales a composer map shift (in MM) and rotates it by mRotation
@param xShift in: shift in x direction (in item units), out: xShift in map units
@param yShift in: shift in y direction (in item units), out: yShift in map units*/

View File

@ -22,6 +22,7 @@
#include <QTextStream>
#include "qgsdataitem.h"
#include "qgsmaphittest.h"
#include "qgsmaplayerlegend.h"
#include "qgspluginlayer.h"
#include "qgsrasterlayer.h"
@ -36,6 +37,9 @@ QgsLayerTreeModel::QgsLayerTreeModel( QgsLayerTreeGroup* rootNode, QObject *pare
, mFlags( ShowLegend | AllowLegendChangeState )
, mAutoCollapseLegendNodesCount( -1 )
, mLegendFilterByScale( 0 )
, mLegendMapViewMupp( 0 )
, mLegendMapViewDpi( 0 )
, mLegendMapViewScale( 0 )
{
connectToRootNode();
@ -66,10 +70,11 @@ QgsLayerTreeModelLegendNode* QgsLayerTreeModel::index2legendNode( const QModelIn
QModelIndex QgsLayerTreeModel::legendNode2index( QgsLayerTreeModelLegendNode* legendNode )
{
QModelIndex parentIndex = node2index( legendNode->parent() );
QModelIndex parentIndex = node2index( legendNode->layerNode() );
Q_ASSERT( parentIndex.isValid() );
int row = mLegendNodes[legendNode->parent()].indexOf( legendNode );
Q_ASSERT( row >= 0 );
int row = mLegendNodes[legendNode->layerNode()].indexOf( legendNode );
if ( row < 0 ) // legend node may be filtered (exists within the list of original nodes, but not in active nodes)
return QModelIndex();
return index( row, 0, parentIndex );
}
@ -145,7 +150,7 @@ QModelIndex QgsLayerTreeModel::parent( const QModelIndex &child ) const
{
QgsLayerTreeModelLegendNode* sym = index2legendNode( child );
Q_ASSERT( sym );
parentNode = sym->parent();
parentNode = sym->layerNode();
}
else
parentNode = n->parent(); // must not be null
@ -457,7 +462,7 @@ bool QgsLayerTreeModel::isIndexSymbologyNode( const QModelIndex& index ) const
QgsLayerTreeLayer* QgsLayerTreeModel::layerNodeForSymbologyNode( const QModelIndex& index ) const
{
QgsLayerTreeModelLegendNode* symNode = index2legendNode( index );
return symNode ? symNode->parent() : 0;
return symNode ? symNode->layerNode() : 0;
}
QList<QgsLayerTreeModelLegendNode*> QgsLayerTreeModel::layerLegendNodes( QgsLayerTreeLayer* nodeLayer )
@ -571,6 +576,60 @@ void QgsLayerTreeModel::setLegendFilterByScale( double scaleDenominator )
refreshLayerLegend( nodeLayer );
}
void QgsLayerTreeModel::setLegendFilterByMap( const QgsMapSettings* settings )
{
if ( settings && settings->hasValidSettings() )
{
mLegendFilterByMapSettings.reset( new QgsMapSettings( *settings ) );
mLegendFilterByMapHitTest.reset( new QgsMapHitTest( *mLegendFilterByMapSettings ) );
mLegendFilterByMapHitTest->run();
}
else
{
if ( !mLegendFilterByMapSettings )
return; // no change
mLegendFilterByMapSettings.reset();
mLegendFilterByMapHitTest.reset();
}
// temporarily disable autocollapse so that legend nodes stay visible
int bkAutoCollapse = autoCollapseLegendNodes();
setAutoCollapseLegendNodes( -1 );
// this could be later done in more efficient way
// by just updating active legend nodes, without refreshing original legend nodes
foreach ( QgsLayerTreeLayer* nodeLayer, mRootNode->findLayers() )
refreshLayerLegend( nodeLayer );
setAutoCollapseLegendNodes( bkAutoCollapse );
}
void QgsLayerTreeModel::setLegendMapViewData( double mapUnitsPerPixel, int dpi, double scale )
{
if ( mLegendMapViewDpi == dpi && mLegendMapViewMupp == mapUnitsPerPixel && mLegendMapViewScale == scale )
return;
mLegendMapViewMupp = mapUnitsPerPixel;
mLegendMapViewDpi = dpi;
mLegendMapViewScale = scale;
// now invalidate legend nodes!
QMap<QgsLayerTreeLayer*, QList<QgsLayerTreeModelLegendNode*> > x;
foreach ( const QList<QgsLayerTreeModelLegendNode*>& lst, mOriginalLegendNodes )
{
foreach ( QgsLayerTreeModelLegendNode* legendNode, lst )
legendNode->invalidateMapBasedData();
}
}
void QgsLayerTreeModel::legendMapViewData( double* mapUnitsPerPixel, int* dpi, double* scale )
{
if ( mapUnitsPerPixel ) *mapUnitsPerPixel = mLegendMapViewMupp;
if ( dpi ) *dpi = mLegendMapViewDpi;
if ( scale ) *scale = mLegendMapViewScale;
}
void QgsLayerTreeModel::nodeWillAddChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
{
Q_ASSERT( node );
@ -697,7 +756,8 @@ void QgsLayerTreeModel::legendNodeDataChanged()
return;
QModelIndex index = legendNode2index( legendNode );
emit dataChanged( index, index );
if ( index.isValid() )
emit dataChanged( index, index );
}
@ -1009,12 +1069,33 @@ const QIcon& QgsLayerTreeModel::iconGroup()
QList<QgsLayerTreeModelLegendNode*> QgsLayerTreeModel::filterLegendNodes( const QList<QgsLayerTreeModelLegendNode*>& nodes )
{
QList<QgsLayerTreeModelLegendNode*> filtered;
foreach ( QgsLayerTreeModelLegendNode* node, nodes )
{
if ( mLegendFilterByScale > 0 && !node->isScaleOK( mLegendFilterByScale ) )
continue;
filtered << node;
if ( mLegendFilterByScale > 0 )
{
foreach ( QgsLayerTreeModelLegendNode* node, nodes )
{
if ( node->isScaleOK( mLegendFilterByScale ) )
filtered << node;
}
}
else if ( mLegendFilterByMapSettings )
{
foreach ( QgsLayerTreeModelLegendNode* node, nodes )
{
QgsSymbolV2* ruleKey = ( QgsSymbolV2* ) node->data( QgsSymbolV2LegendNode::SymbolV2LegacyRuleKeyRole ).value<void*>();
if ( ruleKey )
{
if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( node->layerNode()->layer() ) )
{
if ( mLegendFilterByMapHitTest->symbolsForLayer( vl ).contains( ruleKey ) )
filtered << node;
}
}
else // unknown node type
filtered << node;
}
}
else
return nodes;
return filtered;
}

View File

@ -24,7 +24,9 @@ class QgsLayerTreeNode;
class QgsLayerTreeGroup;
class QgsLayerTreeLayer;
class QgsLayerTreeModelLegendNode;
class QgsMapHitTest;
class QgsMapLayer;
class QgsMapSettings;
/**
@ -105,7 +107,8 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
//! Return legend node for given index. Returns null for invalid index
//! @note added in 2.6
static QgsLayerTreeModelLegendNode* index2legendNode( const QModelIndex& index );
//! Return index for a given legend node. If the legend node does not belong to the layer tree, the result is undefined
//! Return index for a given legend node. If the legend node does not belong to the layer tree, the result is undefined.
//! If the legend node is belongs to the tree but it is filtered out, invalid model index is returned.
//! @note added in 2.6
QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode );
@ -144,6 +147,22 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
void setLegendFilterByScale( double scaleDenominator );
double legendFilterByScale() const { return mLegendFilterByScale; }
//! Force only display of legend nodes which are valid for given map settings.
//! Setting null pointer or invalid map settings will disable the functionality.
//! Ownership of map settings pointer does not change.
//! @note added in 2.6
void setLegendFilterByMap( const QgsMapSettings* settings );
const QgsMapSettings* legendFilterByMap() const { return mLegendFilterByMapSettings.data(); }
//! Give the layer tree model hints about the currently associated map view
//! so that legend nodes that use map units can be scaled currectly
//! @note added in 2.6
void setLegendMapViewData( double mapUnitsPerPixel, int dpi, double scale );
//! Get hints about map view - to be used in legend nodes. Arguments that are not null will receive values.
//! If there are no valid map view data (from previous call to setLegendMapViewData()), returned values are zeros.
//! @note added in 2.6
void legendMapViewData( double *mapUnitsPerPixel, int *dpi, double *scale );
//! Return true if index represents a legend node (instead of layer node)
//! @deprecated use index2legendNode()
Q_DECL_DEPRECATED bool isIndexSymbologyNode( const QModelIndex& index ) const;
@ -218,6 +237,13 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
//! scale denominator for filtering of legend nodes (<= 0 means no filtering)
double mLegendFilterByScale;
QScopedPointer<QgsMapSettings> mLegendFilterByMapSettings;
QScopedPointer<QgsMapHitTest> mLegendFilterByMapHitTest;
double mLegendMapViewMupp;
int mLegendMapViewDpi;
double mLegendMapViewScale;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLayerTreeModel::Flags )

View File

@ -16,6 +16,7 @@
#include "qgslayertreemodellegendnode.h"
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslegendsettings.h"
#include "qgsrasterlayer.h"
#include "qgsrendererv2.h"
@ -35,6 +36,11 @@ QgsLayerTreeModelLegendNode::~QgsLayerTreeModelLegendNode()
{
}
QgsLayerTreeModel* QgsLayerTreeModelLegendNode::model() const
{
return qobject_cast<QgsLayerTreeModel*>( parent() );
}
Qt::ItemFlags QgsLayerTreeModelLegendNode::flags() const
{
return Qt::ItemIsEnabled;
@ -122,11 +128,15 @@ QSizeF QgsLayerTreeModelLegendNode::drawSymbolText( const QgsLegendSettings& set
// -------------------------------------------------------------------------
QgsSymbolV2LegendNode::QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item )
: QgsLayerTreeModelLegendNode( nodeLayer )
QgsSymbolV2LegendNode::QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item, QObject* parent )
: QgsLayerTreeModelLegendNode( nodeLayer, parent )
, mItem( item )
, mSymbolUsesMapUnits( false )
{
updateLabel();
if ( mItem.symbol() )
mSymbolUsesMapUnits = ( mItem.symbol()->outputUnit() != QgsSymbolV2::MM );
}
QgsSymbolV2LegendNode::~QgsSymbolV2LegendNode()
@ -160,7 +170,21 @@ QVariant QgsSymbolV2LegendNode::data( int role ) const
{
QPixmap pix;
if ( mItem.symbol() )
pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), iconSize );
{
double scale, mupp;
int dpi;
if ( model() )
model()->legendMapViewData( &mupp, &dpi, &scale );
bool validData = mupp != 0 && dpi != 0 && scale != 0;
// setup temporary render context
QgsRenderContext context;
context.setScaleFactor( dpi / 25.4 );
context.setRendererScale( scale );
context.setMapToPixel( QgsMapToPixel( mupp ) ); // hope it's ok to leave out other params
pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), iconSize, validData ? &context : 0 );
}
else
{
pix = QPixmap( iconSize );
@ -197,6 +221,10 @@ QVariant QgsSymbolV2LegendNode::data( int role ) const
{
return mItem.ruleKey();
}
else if ( role == SymbolV2LegacyRuleKeyRole )
{
return QVariant::fromValue<void*>( mItem.legacyRuleKey() );
}
return QVariant();
}
@ -274,32 +302,33 @@ QSizeF QgsSymbolV2LegendNode::drawSymbol( const QgsLegendSettings& settings, Ite
double dotsPerMM = context.scaleFactor();
int opacity = 255;
if ( QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( parent()->layer() ) )
if ( QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( layerNode()->layer() ) )
opacity = 255 - ( 255 * vectorLayer->layerTransparency() / 100 );
p->save();
p->setRenderHint( QPainter::Antialiasing );
p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
if ( opacity != 255 && settings.useAdvancedEffects() )
{
//semi transparent layer, so need to draw symbol to an image (to flatten it first)
//create image which is same size as legend rect, in case symbol bleeds outside its alloted space
QImage tempImage = QImage( QSize( width * dotsPerMM, height * dotsPerMM ), QImage::Format_ARGB32 );
QPainter imagePainter( &tempImage );
QSize tempImageSize( width * dotsPerMM, height * dotsPerMM );
QImage tempImage = QImage( tempImageSize, QImage::Format_ARGB32 );
tempImage.fill( Qt::transparent );
imagePainter.translate( dotsPerMM * ( currentXPosition + widthOffset ),
dotsPerMM * ( currentYCoord + heightOffset ) );
s->drawPreviewIcon( &imagePainter, QSize( width * dotsPerMM, height * dotsPerMM ), &context );
QPainter imagePainter( &tempImage );
context.setPainter( &imagePainter );
s->drawPreviewIcon( &imagePainter, tempImageSize, &context );
context.setPainter( ctx->painter );
//reduce opacity of image
imagePainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
imagePainter.fillRect( tempImage.rect(), QColor( 0, 0, 0, opacity ) );
imagePainter.end();
//draw rendered symbol image
p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
p->drawImage( 0, 0, tempImage );
}
else
{
p->translate( currentXPosition + widthOffset, currentYCoord + heightOffset );
p->scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
s->drawPreviewIcon( p, QSize( width * dotsPerMM, height * dotsPerMM ), &context );
}
p->restore();
@ -317,6 +346,16 @@ void QgsSymbolV2LegendNode::setEmbeddedInParent( bool embedded )
}
void QgsSymbolV2LegendNode::invalidateMapBasedData()
{
if ( mSymbolUsesMapUnits )
{
mPixmap = QPixmap();
emit dataChanged();
}
}
void QgsSymbolV2LegendNode::updateLabel()
{
bool showFeatureCount = mLayerNode->customProperty( "showFeatureCount", 0 ).toBool();
@ -426,7 +465,7 @@ QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings& settings,
if ( ctx )
{
QColor itemColor = mColor;
if ( QgsRasterLayer* rasterLayer = dynamic_cast<QgsRasterLayer*>( parent()->layer() ) )
if ( QgsRasterLayer* rasterLayer = dynamic_cast<QgsRasterLayer*>( layerNode()->layer() ) )
{
if ( QgsRasterRenderer* rasterRenderer = rasterLayer->renderer() )
itemColor.setAlpha( rasterRenderer ? rasterRenderer->opacity() * 255.0 : 255 );

View File

@ -20,10 +20,10 @@
#include <QObject>
class QgsLayerTreeLayer;
class QgsLayerTreeModel;
class QgsLegendSettings;
class QgsSymbolV2;
/**
* The QgsLegendRendererItem class is abstract interface for legend items
* returned from QgsMapLayerLegend implementation.
@ -41,11 +41,15 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
enum LegendNodeRoles
{
RuleKeyRole = Qt::UserRole
RuleKeyRole = Qt::UserRole, //!< rule key of the node (QString)
SymbolV2LegacyRuleKeyRole //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
};
/** Return pointer to the parent layer node */
QgsLayerTreeLayer* parent() const { return mLayerNode; }
QgsLayerTreeLayer* layerNode() const { return mLayerNode; }
/** Return pointer to model owning this legend node */
QgsLayerTreeModel* model() const;
/** Return item flags associated with the item. Default implementation returns Qt::ItemIsEnabled. */
virtual Qt::ItemFlags flags() const;
@ -64,6 +68,10 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
virtual bool isScaleOK( double scale ) const { Q_UNUSED( scale ); return true; }
/** Notification from model that information from associated map view has changed.
* Default implementation does nothing. */
virtual void invalidateMapBasedData() {}
struct ItemContext
{
//! Painter
@ -130,7 +138,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
{
public:
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item );
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item, QObject* parent = 0 );
~QgsSymbolV2LegendNode();
virtual Qt::ItemFlags flags() const;
@ -145,6 +153,8 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
virtual bool isScaleOK( double scale ) const { return mItem.isScaleOK( scale ); }
virtual void invalidateMapBasedData();
private:
void updateLabel();
@ -152,6 +162,7 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
QgsLegendSymbolItemV2 mItem;
mutable QPixmap mPixmap; // cached symbol preview
QString mLabel;
bool mSymbolUsesMapUnits;
};

View File

@ -325,7 +325,7 @@ void QgsLegendRenderer::setColumns( QList<Atom>& atomList )
{
if ( QgsLayerTreeModelLegendNode* legendNode = qobject_cast<QgsLayerTreeModelLegendNode*>( atom.nucleons[j].item ) )
{
QString key = QString( "%1-%2" ).arg(( qulonglong )legendNode->parent() ).arg( atom.column );
QString key = QString( "%1-%2" ).arg(( qulonglong )legendNode->layerNode() ).arg( atom.column );
maxSymbolWidth[key] = qMax( atom.nucleons[j].symbolSize.width(), maxSymbolWidth[key] );
}
}
@ -337,7 +337,7 @@ void QgsLegendRenderer::setColumns( QList<Atom>& atomList )
{
if ( QgsLayerTreeModelLegendNode* legendNode = qobject_cast<QgsLayerTreeModelLegendNode*>( atom.nucleons[j].item ) )
{
QString key = QString( "%1-%2" ).arg(( qulonglong )legendNode->parent() ).arg( atom.column );
QString key = QString( "%1-%2" ).arg(( qulonglong )legendNode->layerNode() ).arg( atom.column );
double space = mSettings.style( QgsComposerLegendStyle::Symbol ).margin( QgsComposerLegendStyle::Right ) +
mSettings.style( QgsComposerLegendStyle::SymbolLabel ).margin( QgsComposerLegendStyle::Left );
atom.nucleons[j].labelXOffset = maxSymbolWidth[key] + space;

View File

@ -0,0 +1,72 @@
#include "qgsmaphittest.h"
#include "qgsmaplayerregistry.h"
#include "qgsrendercontext.h"
#include "qgsrendererv2.h"
#include "qgsvectorlayer.h"
QgsMapHitTest::QgsMapHitTest( const QgsMapSettings& settings )
: mSettings( settings )
{
}
void QgsMapHitTest::run()
{
// TODO: do we need this temp image?
QImage tmpImage( mSettings.outputSize(), mSettings.outputImageFormat() );
tmpImage.setDotsPerMeterX( mSettings.outputDpi() * 25.4 );
tmpImage.setDotsPerMeterY( mSettings.outputDpi() * 25.4 );
QPainter painter( &tmpImage );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mSettings );
context.setPainter( &painter ); // we are not going to draw anything, but we still need a working painter
foreach ( QString layerID, mSettings.layers() )
{
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) );
if ( !vl || !vl->rendererV2() )
continue;
if ( vl->hasScaleBasedVisibility() && ( mSettings.scale() < vl->minimumScale() || mSettings.scale() > vl->maximumScale() ) )
{
mHitTest[vl] = SymbolV2Set(); // no symbols -> will not be shown
continue;
}
if ( mSettings.hasCrsTransformEnabled() )
{
context.setCoordinateTransform( mSettings.layerTransfrom( vl ) );
context.setExtent( mSettings.outputExtentToLayerExtent( vl, mSettings.visibleExtent() ) );
}
SymbolV2Set& usedSymbols = mHitTest[vl];
runHitTestLayer( vl, usedSymbols, context );
}
painter.end();
}
void QgsMapHitTest::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context )
{
QgsFeatureRendererV2* r = vl->rendererV2();
bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRendererV2::MoreSymbolsPerFeature;
r->startRender( context, vl->pendingFields() );
QgsFeature f;
QgsFeatureRequest request( context.extent() );
request.setFlags( QgsFeatureRequest::ExactIntersect );
QgsFeatureIterator fi = vl->getFeatures( request );
while ( fi.nextFeature( f ) )
{
if ( moreSymbolsPerFeature )
{
foreach ( QgsSymbolV2* s, r->originalSymbolsForFeature( f ) )
usedSymbols.insert( s );
}
else
usedSymbols.insert( r->originalSymbolForFeature( f ) );
}
r->stopRender( context );
}

39
src/core/qgsmaphittest.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef QGSMAPHITTEST_H
#define QGSMAPHITTEST_H
#include "qgsmapsettings.h"
#include <QSet>
class QgsRenderContext;
class QgsSymbolV2;
class QgsVectorLayer;
/**
* Class that runs a hit test with given map settings. Based on the hit test it returns which symbols
* will be visible on the map - this is useful for content based legend.
*
* @note added in 2.6
*/
class CORE_EXPORT QgsMapHitTest
{
public:
QgsMapHitTest( const QgsMapSettings& settings );
void run();
QSet<QgsSymbolV2*> symbolsForLayer( QgsVectorLayer* layer ) const { return mHitTest[layer]; }
protected:
typedef QSet<QgsSymbolV2*> SymbolV2Set;
typedef QMap<QgsVectorLayer*, SymbolV2Set> HitTest;
void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context );
QgsMapSettings mSettings;
HitTest mHitTest;
};
#endif // QGSMAPHITTEST_H

View File

@ -324,6 +324,14 @@ class CORE_EXPORT QgsMapRenderer : public QObject
//! @note added in 2.4
const QgsMapSettings& mapSettings();
/** Convenience function to project an extent into the layer source
* CRS, but also split it into two extents if it crosses
* the +/- 180 degree line. Modifies the given extent to be in the
* source CRS coordinates, and if it was split, returns true, and
* also sets the contents of the r2 parameter
*/
bool splitLayersExtent( QgsMapLayer* layer, QgsRectangle& extent, QgsRectangle& r2 );
signals:
//! @deprecated in 2.4 - not emitted anymore
@ -371,14 +379,6 @@ class CORE_EXPORT QgsMapRenderer : public QObject
//! adjust extent to fit the pixmap size
void adjustExtentToSize();
/** Convenience function to project an extent into the layer source
* CRS, but also split it into two extents if it crosses
* the +/- 180 degree line. Modifies the given extent to be in the
* source CRS coordinates, and if it was split, returns true, and
* also sets the contents of the r2 parameter
*/
bool splitLayersExtent( QgsMapLayer* layer, QgsRectangle& extent, QgsRectangle& r2 );
//! indicates drawing in progress
static bool mDrawing;
@ -437,7 +437,6 @@ class CORE_EXPORT QgsMapRenderer : public QObject
QgsMapSettings mMapSettings;
QHash< QString, QgsLayerCoordinateTransform > mLayerCoordinateTransformInfo;
};
#endif

View File

@ -769,7 +769,7 @@ bool QgsVectorLayer::countSymbolFeatures( bool showProgress )
QgsFeature f;
while ( fit.nextFeature( f ) )
{
QgsSymbolV2List featureSymbolList = mRendererV2->symbolsForFeature( f );
QgsSymbolV2List featureSymbolList = mRendererV2->originalSymbolsForFeature( f );
for ( QgsSymbolV2List::iterator symbolIt = featureSymbolList.begin(); symbolIt != featureSymbolList.end(); ++symbolIt )
{
mSymbolFeatureCountMap[*symbolIt] += 1;

View File

@ -196,6 +196,40 @@ QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForValue( QVariant value )
}
QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForFeature( QgsFeature& feature )
{
QgsSymbolV2* symbol = originalSymbolForFeature( feature );
if ( !symbol )
return 0;
if ( !mRotation.data() && !mSizeScale.data() )
return symbol; // no data-defined rotation/scaling - just return the symbol
// find out rotation, size scale
const double rotation = mRotation.data() ? mRotation->evaluate( feature ).toDouble() : 0;
const double sizeScale = mSizeScale.data() ? mSizeScale->evaluate( feature ).toDouble() : 1.;
// take a temporary symbol (or create it if doesn't exist)
QgsSymbolV2* tempSymbol = mTempSymbols[symbol];
// modify the temporary symbol and return it
if ( tempSymbol->type() == QgsSymbolV2::Marker )
{
QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( tempSymbol );
if ( mRotation.data() ) markerSymbol->setAngle( rotation );
markerSymbol->setSize( sizeScale * static_cast<QgsMarkerSymbolV2*>( symbol )->size() );
markerSymbol->setScaleMethod( mScaleMethod );
}
else if ( tempSymbol->type() == QgsSymbolV2::Line )
{
QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( tempSymbol );
lineSymbol->setWidth( sizeScale * static_cast<QgsLineSymbolV2*>( symbol )->width() );
}
return tempSymbol;
}
QgsSymbolV2* QgsCategorizedSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature )
{
const QgsAttributes& attrs = feature.attributes();
QVariant value;
@ -220,33 +254,10 @@ QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForFeature( QgsFeature& featu
return symbolForValue( QVariant( "" ) );
}
if ( !mRotation.data() && !mSizeScale.data() )
return symbol; // no data-defined rotation/scaling - just return the symbol
// find out rotation, size scale
const double rotation = mRotation.data() ? mRotation->evaluate( feature ).toDouble() : 0;
const double sizeScale = mSizeScale.data() ? mSizeScale->evaluate( feature ).toDouble() : 1.;
// take a temporary symbol (or create it if doesn't exist)
QgsSymbolV2* tempSymbol = mTempSymbols[value.toString()];
// modify the temporary symbol and return it
if ( tempSymbol->type() == QgsSymbolV2::Marker )
{
QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( tempSymbol );
if ( mRotation.data() ) markerSymbol->setAngle( rotation );
markerSymbol->setSize( sizeScale * static_cast<QgsMarkerSymbolV2*>( symbol )->size() );
markerSymbol->setScaleMethod( mScaleMethod );
}
else if ( tempSymbol->type() == QgsSymbolV2::Line )
{
QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( tempSymbol );
lineSymbol->setWidth( sizeScale * static_cast<QgsLineSymbolV2*>( symbol )->width() );
}
return tempSymbol;
return symbol;
}
int QgsCategorizedSymbolRendererV2::categoryIndexForValue( QVariant val )
{
for ( int i = 0; i < mCategories.count(); i++ )
@ -405,7 +416,7 @@ void QgsCategorizedSymbolRendererV2::startRender( QgsRenderContext& context, con
tempSymbol->setRenderHints(( mRotation.data() ? QgsSymbolV2::DataDefinedRotation : 0 ) |
( mSizeScale.data() ? QgsSymbolV2::DataDefinedSizeScale : 0 ) );
tempSymbol->startRender( context, &fields );
mTempSymbols[ it->value().toString()] = tempSymbol;
mTempSymbols[ it->symbol()] = tempSymbol;
}
}
}
@ -417,7 +428,7 @@ void QgsCategorizedSymbolRendererV2::stopRender( QgsRenderContext& context )
it->symbol()->stopRender( context );
// cleanup mTempSymbols
QHash<QString, QgsSymbolV2*>::iterator it2 = mTempSymbols.begin();
QHash<QgsSymbolV2*, QgsSymbolV2*>::iterator it2 = mTempSymbols.begin();
for ( ; it2 != mTempSymbols.end(); ++it2 )
{
it2.value()->stopRender( context );

View File

@ -77,6 +77,8 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context );
@ -205,7 +207,7 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
bool mCounting;
//! temporary symbols, used for data-defined rotation and scaling
QHash<QString, QgsSymbolV2*> mTempSymbols;
QHash<QgsSymbolV2*, QgsSymbolV2*> mTempSymbols;
void rebuildHash();

View File

@ -191,23 +191,7 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForValue( double value )
QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature )
{
const QgsAttributes& attrs = feature.attributes();
QVariant value;
if ( mAttrNum < 0 || mAttrNum >= attrs.count() )
{
value = mExpression->evaluate( &feature );
}
else
{
value = attrs[mAttrNum];
}
// Null values should not be categorized
if ( value.isNull() )
return NULL;
// find the right category
QgsSymbolV2* symbol = symbolForValue( value.toDouble() );
QgsSymbolV2* symbol = originalSymbolForFeature( feature );
if ( symbol == NULL )
return NULL;
@ -237,6 +221,27 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature
return tempSymbol;
}
QgsSymbolV2* QgsGraduatedSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature )
{
const QgsAttributes& attrs = feature.attributes();
QVariant value;
if ( mAttrNum < 0 || mAttrNum >= attrs.count() )
{
value = mExpression->evaluate( &feature );
}
else
{
value = attrs[mAttrNum];
}
// Null values should not be categorized
if ( value.isNull() )
return NULL;
// find the right category
return symbolForValue( value.toDouble() );
}
void QgsGraduatedSymbolRendererV2::startRender( QgsRenderContext& context, const QgsFields& fields )
{
mCounting = context.rendererScale() == 0.0;

View File

@ -75,6 +75,8 @@ class CORE_EXPORT QgsGraduatedSymbolRendererV2 : public QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context );

View File

@ -377,6 +377,13 @@ QgsSymbolV2* QgsInvertedPolygonRenderer::symbolForFeature( QgsFeature& feature )
return mSubRenderer->symbolForFeature( feature );
}
QgsSymbolV2* QgsInvertedPolygonRenderer::originalSymbolForFeature( QgsFeature& feat )
{
if ( !mSubRenderer )
return 0;
return mSubRenderer->originalSymbolForFeature( feat );
}
QgsSymbolV2List QgsInvertedPolygonRenderer::symbolsForFeature( QgsFeature& feature )
{
if ( !mSubRenderer )
@ -386,6 +393,13 @@ QgsSymbolV2List QgsInvertedPolygonRenderer::symbolsForFeature( QgsFeature& featu
return mSubRenderer->symbolsForFeature( feature );
}
QgsSymbolV2List QgsInvertedPolygonRenderer::originalSymbolsForFeature( QgsFeature& feat )
{
if ( !mSubRenderer )
return QgsSymbolV2List();
return mSubRenderer->originalSymbolsForFeature( feat );
}
QgsSymbolV2List QgsInvertedPolygonRenderer::symbols()
{
if ( !mSubRenderer )

View File

@ -81,8 +81,12 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsLegendSymbologyList legendSymbologyItems( QSize iconSize );
/** Proxy that will call this method on the embedded renderer.
@note not available in python bindings

View File

@ -586,3 +586,11 @@ QgsSymbolV2List QgsFeatureRendererV2::symbolsForFeature( QgsFeature& feat )
if ( s ) lst.append( s );
return lst;
}
QgsSymbolV2List QgsFeatureRendererV2::originalSymbolsForFeature( QgsFeature& feat )
{
QgsSymbolV2List lst;
QgsSymbolV2* s = originalSymbolForFeature( feat );
if ( s ) lst.append( s );
return lst;
}

View File

@ -85,6 +85,14 @@ class CORE_EXPORT QgsFeatureRendererV2
*/
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature ) = 0;
/**
* Return symbol for feature. The difference compared to symbolForFeature() is that it returns original
* symbol which can be used as an identifier for renderer's rule - the former may return a temporary replacement
* of a symbol for use in rendering.
* @note added in 2.6
*/
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature ) { return symbolForFeature( feature ); }
virtual void startRender( QgsRenderContext& context, const QgsFields& fields ) = 0;
//! @deprecated since 2.4 - not using QgsVectorLayer directly anymore
@ -201,6 +209,11 @@ class CORE_EXPORT QgsFeatureRendererV2
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
//! Equivalent of originalSymbolsForFeature() call
//! extended to support renderers that may use more symbols per feature - similar to symbolsForFeature()
//! @note added in 2.6
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
protected:
QgsFeatureRendererV2( QString type );

View File

@ -1056,6 +1056,11 @@ QgsSymbolV2List QgsRuleBasedRendererV2::symbolsForFeature( QgsFeature& feat )
return mRootRule->symbolsForFeature( feat );
}
QgsSymbolV2List QgsRuleBasedRendererV2::originalSymbolsForFeature( QgsFeature& feat )
{
return mRootRule->symbolsForFeature( feat );
}
QgsRuleBasedRendererV2* QgsRuleBasedRendererV2::convertFromRenderer( const QgsFeatureRendererV2* renderer )
{
if ( renderer->type() == "RuleRenderer" )

View File

@ -276,6 +276,8 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
//! returns bitwise OR-ed capabilities of the renderer
//! \note added in 2.0
virtual int capabilities() { return MoreSymbolsPerFeature | Filter | ScaleDependent; }

View File

@ -69,6 +69,12 @@ QgsSymbolV2* QgsSingleSymbolRendererV2::symbolForFeature( QgsFeature& feature )
return mTempSymbol.data();
}
QgsSymbolV2* QgsSingleSymbolRendererV2::originalSymbolForFeature( QgsFeature& feature )
{
Q_UNUSED( feature );
return mSymbol.data();
}
void QgsSingleSymbolRendererV2::startRender( QgsRenderContext& context, const QgsFields& fields )
{
if ( !mSymbol.data() ) return;

View File

@ -31,6 +31,8 @@ class CORE_EXPORT QgsSingleSymbolRendererV2 : public QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context );

View File

@ -522,7 +522,7 @@ QIcon QgsSymbolLayerV2Utils::symbolPreviewIcon( QgsSymbolV2* symbol, QSize size
return QIcon( symbolPreviewPixmap( symbol, size ) );
}
QPixmap QgsSymbolLayerV2Utils::symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size )
QPixmap QgsSymbolLayerV2Utils::symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size, QgsRenderContext* customContext )
{
Q_ASSERT( symbol );
@ -531,7 +531,9 @@ QPixmap QgsSymbolLayerV2Utils::symbolPreviewPixmap( QgsSymbolV2* symbol, QSize s
QPainter painter;
painter.begin( &pixmap );
painter.setRenderHint( QPainter::Antialiasing );
symbol->drawPreviewIcon( &painter, size );
if ( customContext )
customContext->setPainter( &painter );
symbol->drawPreviewIcon( &painter, size, customContext );
painter.end();
return pixmap;
}

View File

@ -108,7 +108,8 @@ class CORE_EXPORT QgsSymbolLayerV2Utils
static void drawStippledBackround( QPainter* painter, QRect rect );
static QPixmap symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size );
//! @note customContext parameter added in 2.6
static QPixmap symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size, QgsRenderContext* customContext = 0 );
static QPixmap colorRampPreviewPixmap( QgsVectorColorRampV2* ramp, QSize size );
/**Returns the maximum estimated bleed for the symbol */

View File

@ -209,7 +209,7 @@ QgsMapLayer* QgsLayerTreeView::layerForIndex( const QModelIndex& index ) const
// possibly a legend node
QgsLayerTreeModelLegendNode* legendNode = layerTreeModel()->index2legendNode( index );
if ( legendNode )
return legendNode->parent()->layer();
return legendNode->layerNode()->layer();
}
return 0;
@ -234,7 +234,7 @@ QgsLayerTreeGroup* QgsLayerTreeView::currentGroupNode() const
if ( QgsLayerTreeModelLegendNode* legendNode = layerTreeModel()->index2legendNode( selectionModel()->currentIndex() ) )
{
QgsLayerTreeLayer* parent = legendNode->parent();
QgsLayerTreeLayer* parent = legendNode->layerNode();
if ( QgsLayerTree::isGroup( parent->parent() ) )
return QgsLayerTree::toGroup( parent->parent() );
}

View File

@ -527,6 +527,32 @@ static QgsLayerTreeModelLegendNode* _findLegendNodeForRule( QgsLayerTreeModel* l
}
static QgsRectangle _parseBBOX( const QString& bboxStr, bool* ok )
{
*ok = false;
QgsRectangle bbox;
QStringList lst = bboxStr.split( "," );
if ( lst.count() != 4 )
return bbox;
bool convOk;
bbox.setXMinimum( lst[0].toDouble( &convOk ) );
if ( !convOk ) return bbox;
bbox.setYMinimum( lst[1].toDouble( &convOk ) );
if ( !convOk ) return bbox;
bbox.setXMaximum( lst[2].toDouble( &convOk ) );
if ( !convOk ) return bbox;
bbox.setYMaximum( lst[3].toDouble( &convOk ) );
if ( !convOk ) return bbox;
if ( bbox.isEmpty() ) return bbox;
*ok = true;
return bbox;
}
QImage* QgsWMSServer::getLegendGraphics()
{
if ( !mConfigParser || !mMapRenderer )
@ -542,6 +568,22 @@ QImage* QgsWMSServer::getLegendGraphics()
throw QgsMapServiceException( "FormatNotSpecified", "FORMAT is mandatory for GetLegendGraphic operation" );
}
bool contentBasedLegend = false;
QgsRectangle contentBasedLegendExtent;
if ( mParameters.contains( "BBOX" ) )
{
contentBasedLegend = true;
bool bboxOk;
contentBasedLegendExtent = _parseBBOX( mParameters["BBOX"], &bboxOk );
if ( !bboxOk )
throw QgsMapServiceException( "InvalidParameterValue", "Invalid BBOX parameter" );
if ( mParameters.contains( "RULE" ) )
throw QgsMapServiceException( "InvalidParameterValue", "BBOX parameter cannot be combined with RULE" );
}
QStringList layersList, stylesList;
if ( readLayersAndStyles( layersList, stylesList ) != 0 )
@ -622,6 +664,41 @@ QImage* QgsWMSServer::getLegendGraphics()
if ( scaleDenominator > 0 )
legendModel.setLegendFilterByScale( scaleDenominator );
if ( contentBasedLegend )
{
HitTest hitTest;
getMap( &hitTest );
foreach ( QgsLayerTreeNode* node, rootGroup.children() )
{
Q_ASSERT( QgsLayerTree::isLayer( node ) );
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( nodeLayer->layer() );
if ( !vl || !vl->rendererV2() )
continue;
const SymbolV2Set& usedSymbols = hitTest[vl];
QList<int> order;
int i = 0;
foreach ( const QgsLegendSymbolItemV2& legendItem, vl->rendererV2()->legendSymbolItemsV2() )
{
if ( usedSymbols.contains( legendItem.legacyRuleKey() ) )
order.append( i );
++i;
}
// either remove the whole layer or just filter out some items
if ( order.isEmpty() )
rootGroup.removeChildNode( nodeLayer );
else
{
QgsMapLayerLegendUtils::setLegendNodeOrder( nodeLayer, order );
legendModel.refreshLayerLegend( nodeLayer );
}
}
}
// find out DPI
QImage* tmpImage = createImage( 1, 1 );
if ( !tmpImage )
@ -643,6 +720,13 @@ QImage* QgsWMSServer::getLegendGraphics()
// TODO: not available: layer font color
legendSettings.setFontColor( itemFontColor );
if ( contentBasedLegend )
{
legendSettings.setMapScale( mMapRenderer->scale() );
double scaleFactor = mMapRenderer->outputUnits() == QgsMapRenderer::Millimeters ? mMapRenderer->outputDpi() / 25.4 : 1.0;
legendSettings.setMmPerMapUnit( 1 / ( mMapRenderer->mapUnitsPerPixel() * scaleFactor ) );
}
if ( !rule.isEmpty() )
{
//create second image with the right dimensions
@ -706,6 +790,69 @@ QImage* QgsWMSServer::getLegendGraphics()
}
void QgsWMSServer::runHitTest( QPainter* painter, HitTest& hitTest )
{
QPaintDevice* thePaintDevice = painter->device();
// setup QgsRenderContext in the same way as QgsMapRenderer does
QgsRenderContext context;
context.setPainter( painter ); // we are not going to draw anything, but we still need a working painter
context.setRenderingStopped( false );
context.setRasterScaleFactor(( thePaintDevice->logicalDpiX() + thePaintDevice->logicalDpiY() ) / 2.0 / mMapRenderer->outputDpi() );
context.setScaleFactor( mMapRenderer->outputUnits() == QgsMapRenderer::Millimeters ? mMapRenderer->outputDpi() / 25.4 : 1.0 );
context.setRendererScale( mMapRenderer->scale() );
context.setMapToPixel( *mMapRenderer->coordinateTransform() );
context.setExtent( mMapRenderer->extent() );
foreach ( QString layerID, mMapRenderer->layerSet() )
{
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) );
if ( !vl || !vl->rendererV2() )
continue;
if ( vl->hasScaleBasedVisibility() && ( mMapRenderer->scale() < vl->minimumScale() || mMapRenderer->scale() > vl->maximumScale() ) )
{
hitTest[vl] = SymbolV2Set(); // no symbols -> will not be shown
continue;
}
if ( mMapRenderer->hasCrsTransformEnabled() )
{
QgsRectangle r1 = mMapRenderer->extent(), r2;
mMapRenderer->splitLayersExtent( vl, r1, r2 );
if ( !r1.isFinite() || !r2.isFinite() ) //there was a problem transforming the extent. Skip the layer
continue;
context.setCoordinateTransform( mMapRenderer->transformation( vl ) );
context.setExtent( r1 );
}
SymbolV2Set& usedSymbols = hitTest[vl];
runHitTestLayer( vl, usedSymbols, context );
}
}
void QgsWMSServer::runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context )
{
QgsFeatureRendererV2* r = vl->rendererV2();
bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRendererV2::MoreSymbolsPerFeature;
r->startRender( context, vl->pendingFields() );
QgsFeature f;
QgsFeatureRequest request( context.extent() );
request.setFlags( QgsFeatureRequest::ExactIntersect );
QgsFeatureIterator fi = vl->getFeatures( request );
while ( fi.nextFeature( f ) )
{
if ( moreSymbolsPerFeature )
{
foreach ( QgsSymbolV2* s, r->originalSymbolsForFeature( f ) )
usedSymbols.insert( s );
}
else
usedSymbols.insert( r->originalSymbolForFeature( f ) );
}
r->stopRender( context );
}
void QgsWMSServer::legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace,
double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight,
@ -961,7 +1108,7 @@ QImage* QgsWMSServer::printCompositionToImage( QgsComposition* c ) const
}
#endif
QImage* QgsWMSServer::getMap()
QImage* QgsWMSServer::getMap( HitTest* hitTest )
{
if ( !checkMaximumWidthHeight() )
{
@ -983,7 +1130,11 @@ QImage* QgsWMSServer::getMap()
applyOpacities( layersList, bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
mMapRenderer->render( &thePainter );
if ( hitTest )
runHitTest( &thePainter, *hitTest );
else
mMapRenderer->render( &thePainter );
if ( mConfigParser )
{
//draw configuration format specific overlay items

View File

@ -43,6 +43,7 @@ class QgsRectangle;
class QgsRenderContext;
class QgsVectorLayer;
class QgsSymbol;
class QgsSymbolV2;
class QColor;
class QFile;
class QFont;
@ -75,9 +76,14 @@ class QgsWMSServer: public QgsOWSServer
/**Returns the map legend as an image (or a null pointer in case of error). The caller takes ownership
of the image object*/
QImage* getLegendGraphics();
typedef QSet<QgsSymbolV2*> SymbolV2Set;
typedef QMap<QgsVectorLayer*, SymbolV2Set> HitTest;
/**Returns the map as an image (or a null pointer in case of error). The caller takes ownership
of the image object)*/
QImage* getMap();
of the image object). If an instance to existing hit test structure is passed, instead of rendering
it will fill the structure with symbols that would be used for rendering */
QImage* getMap( HitTest* hitTest = 0 );
/**Returns an SLD file with the style of the requested layer. Exception is raised in case of troubles :-)*/
QDomDocument getStyle();
/**Returns an SLD file with the styles of the requested layers. Exception is raised in case of troubles :-)*/
@ -155,6 +161,11 @@ class QgsWMSServer: public QgsOWSServer
@param scaleDenominator Filter out layer if scale based visibility does not match (or use -1 if no scale restriction)*/
QStringList layerSet( const QStringList& layersList, const QStringList& stylesList, const QgsCoordinateReferenceSystem& destCRS, double scaleDenominator = -1 ) const;
/**Record which symbols would be used if the map was in the current configuration of mMapRenderer. This is useful for content-based legend*/
void runHitTest( QPainter* painter, HitTest& hitTest );
/**Record which symbols within one layer would be rendered with the given renderer context*/
void runHitTestLayer( QgsVectorLayer* vl, SymbolV2Set& usedSymbols, QgsRenderContext& context );
/**Read legend parameter from the request or from the first print composer in the project*/
void legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace,
double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight, QFont& layerFont, QFont& itemFont, QColor& layerFontColor, QColor& itemFontColor );

View File

@ -55,8 +55,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>375</width>
<height>1319</height>
<width>372</width>
<height>1327</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainLayout">
@ -354,6 +354,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mFilterByMapToolButton">
<property name="toolTip">
<string>Filter Legend By Map Content</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionFilter.png</normaloff>:/images/themes/default/mActionFilter.png</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
@ -845,6 +865,43 @@
<header>qgslayertreeview.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>scrollArea</tabstop>
<tabstop>mTitleLineEdit</tabstop>
<tabstop>mMapComboBox</tabstop>
<tabstop>mWrapCharLineEdit</tabstop>
<tabstop>mTitleAlignCombo</tabstop>
<tabstop>mCheckBoxAutoUpdate</tabstop>
<tabstop>mUpdateAllPushButton</tabstop>
<tabstop>mItemTreeView</tabstop>
<tabstop>mMoveDownToolButton</tabstop>
<tabstop>mMoveUpToolButton</tabstop>
<tabstop>mAddGroupToolButton</tabstop>
<tabstop>mAddToolButton</tabstop>
<tabstop>mRemoveToolButton</tabstop>
<tabstop>mEditPushButton</tabstop>
<tabstop>mCountToolButton</tabstop>
<tabstop>mFilterByMapToolButton</tabstop>
<tabstop>mTitleFontButton</tabstop>
<tabstop>mLayerFontButton</tabstop>
<tabstop>mGroupFontButton</tabstop>
<tabstop>mItemFontButton</tabstop>
<tabstop>mFontColorButton</tabstop>
<tabstop>mColumnCountSpinBox</tabstop>
<tabstop>mEqualColumnWidthCheckBox</tabstop>
<tabstop>mSplitLayerCheckBox</tabstop>
<tabstop>mSymbolWidthSpinBox</tabstop>
<tabstop>mSymbolHeightSpinBox</tabstop>
<tabstop>mWmsLegendWidthSpinBox</tabstop>
<tabstop>mWmsLegendHeightSpinBox</tabstop>
<tabstop>mGroupSpaceSpinBox</tabstop>
<tabstop>mLayerSpaceSpinBox</tabstop>
<tabstop>mSymbolSpaceSpinBox</tabstop>
<tabstop>mIconLabelSpaceSpinBox</tabstop>
<tabstop>mBoxSpaceSpinBox</tabstop>
<tabstop>mColumnSpaceSpinBox</tabstop>
<tabstop>mTitleSpaceBottomSpinBox</tabstop>
</tabstops>
<resources>
<include location="../../images/images.qrc"/>
</resources>