[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 //! @note added in 2.6
bool autoUpdateModel() const; 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 //setters and getters
void setTitle( const QString& t ); void setTitle( const QString& t );
QString title() const; QString title() const;

View File

@ -111,6 +111,10 @@ class QgsComposerMap : QgsComposerItem
/** \brief Create cache image */ /** \brief Create cache image */
void cache(); 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*/ /** \brief Get identification number*/
int id() const; int id() const;
@ -737,6 +741,10 @@ class QgsComposerMap : QgsComposerItem
void connectMapOverviewSignals() /Deprecated/; 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: signals:
void extentChanged(); void extentChanged();

View File

@ -85,7 +85,8 @@ class QgsLayerTreeModel : QAbstractItemModel
//! Return legend node for given index. Returns null for invalid index //! Return legend node for given index. Returns null for invalid index
//! @note added in 2.6 //! @note added in 2.6
static QgsLayerTreeModelLegendNode* index2legendNode( const QModelIndex& index ); 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 //! @note added in 2.6
QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode ); QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode );
@ -124,6 +125,22 @@ class QgsLayerTreeModel : QAbstractItemModel
void setLegendFilterByScale( double scaleDenominator ); void setLegendFilterByScale( double scaleDenominator );
double legendFilterByScale() const; 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) //! Return true if index represents a legend node (instead of layer node)
//! @deprecated use index2legendNode() //! @deprecated use index2legendNode()
bool isIndexSymbologyNode( const QModelIndex& index ) const /Deprecated/; bool isIndexSymbologyNode( const QModelIndex& index ) const /Deprecated/;

View File

@ -19,11 +19,15 @@ class QgsLayerTreeModelLegendNode : QObject
enum LegendNodeRoles 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 */ /** 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. */ /** Return item flags associated with the item. Default implementation returns Qt::ItemIsEnabled. */
virtual Qt::ItemFlags flags() const; virtual Qt::ItemFlags flags() const;
@ -42,6 +46,10 @@ class QgsLayerTreeModelLegendNode : QObject
virtual bool isScaleOK( double scale ) const; 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 struct ItemContext
{ {
//! Painter //! Painter
@ -109,7 +117,7 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
#include <qgslayertreemodellegendnode.h> #include <qgslayertreemodellegendnode.h>
%End %End
public: public:
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item ); QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item, QObject* parent /TransferThis/ = 0 );
~QgsSymbolV2LegendNode(); ~QgsSymbolV2LegendNode();
virtual Qt::ItemFlags flags() const; virtual Qt::ItemFlags flags() const;
@ -123,6 +131,8 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
void setUserLabel( const QString& userLabel ); void setUserLabel( const QString& userLabel );
virtual bool isScaleOK( double scale ) const; virtual bool isScaleOK( double scale ) const;
virtual void invalidateMapBasedData();
}; };

View File

@ -287,6 +287,14 @@ class QgsMapRenderer : QObject
//! @note added in 2.4 //! @note added in 2.4
const QgsMapSettings& mapSettings(); 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: signals:
//! @deprecated in 2.4 - not emitted anymore //! @deprecated in 2.4 - not emitted anymore
@ -331,11 +339,4 @@ class QgsMapRenderer : QObject
//! adjust extent to fit the pixmap size //! adjust extent to fit the pixmap size
void adjustExtentToSize(); 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* symbolForFeature( QgsFeature& feature );
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );
virtual void startRender( QgsRenderContext& context, const QgsFields& fields ); virtual void startRender( QgsRenderContext& context, const QgsFields& fields );
virtual void stopRender( QgsRenderContext& context ); virtual void stopRender( QgsRenderContext& context );

View File

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

View File

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

View File

@ -60,6 +60,14 @@ class QgsFeatureRendererV2
*/ */
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature ) = 0; 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; virtual void startRender( QgsRenderContext& context, const QgsFields& fields ) = 0;
//! @deprecated since 2.4 - not using QgsVectorLayer directly anymore //! @deprecated since 2.4 - not using QgsVectorLayer directly anymore
@ -175,6 +183,11 @@ class QgsFeatureRendererV2
//! @note added in 1.9 //! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat ); 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: protected:
QgsFeatureRendererV2( QString type ); QgsFeatureRendererV2( QString type );

View File

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

View File

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

View File

@ -68,7 +68,8 @@ class QgsSymbolLayerV2Utils
static void drawStippledBackround( QPainter* painter, QRect rect ); 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 ); static QPixmap colorRampPreviewPixmap( QgsVectorColorRampV2* ramp, QSize size );
/**Returns the maximum estimated bleed for the symbol */ /**Returns the maximum estimated bleed for the symbol */

View File

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

View File

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

View File

@ -2333,6 +2333,14 @@ void QgisApp::initLayerTreeView()
btnVisibilityPresets->setPopupMode( QToolButton::InstantPopup ); btnVisibilityPresets->setPopupMode( QToolButton::InstantPopup );
btnVisibilityPresets->setMenu( QgsVisibilityPresets::instance()->menu() ); 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 // expand / collapse tool buttons
QToolButton* btnExpandAll = new QToolButton; QToolButton* btnExpandAll = new QToolButton;
btnExpandAll->setAutoRaise( true ); btnExpandAll->setAutoRaise( true );
@ -2353,6 +2361,7 @@ void QgisApp::initLayerTreeView()
toolbarLayout->setContentsMargins( QMargins( 5, 0, 5, 0 ) ); toolbarLayout->setContentsMargins( QMargins( 5, 0, 5, 0 ) );
toolbarLayout->addWidget( btnAddGroup ); toolbarLayout->addWidget( btnAddGroup );
toolbarLayout->addWidget( btnVisibilityPresets ); toolbarLayout->addWidget( btnVisibilityPresets );
toolbarLayout->addWidget( mBtnFilterLegend );
toolbarLayout->addWidget( btnExpandAll ); toolbarLayout->addWidget( btnExpandAll );
toolbarLayout->addWidget( btnCollapseAll ); toolbarLayout->addWidget( btnCollapseAll );
toolbarLayout->addWidget( btnRemoveItem ); toolbarLayout->addWidget( btnRemoveItem );
@ -3817,6 +3826,8 @@ bool QgisApp::addProject( QString projectFile )
mMapCanvas->updateScale(); mMapCanvas->updateScale();
QgsDebugMsg( "Scale restored..." ); QgsDebugMsg( "Scale restored..." );
setFilterLegendByMapEnabled( QgsProject::instance()->readBoolEntry( "Legend", "filterByMap" ) );
QSettings settings; QSettings settings;
// does the project have any macros? // does the project have any macros?
@ -4213,6 +4224,38 @@ void QgisApp::activateDeuteranopePreview()
mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewDeuteranope ); 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() void QgisApp::saveMapAsImage()
{ {
QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) ); 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; mTrustedMacros = false;
setFilterLegendByMapEnabled( false );
deletePrintComposers(); deletePrintComposers();
removeAnnotationItems(); removeAnnotationItems();
// clear out any stuff from project // clear out any stuff from project
@ -8595,6 +8640,9 @@ void QgisApp::layersWereAdded( QList<QgsMapLayer *> theLayers )
void QgisApp::showExtents() 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() ) if ( !mToggleExtentsViewButton->isChecked() )
{ {
return; return;
@ -9710,6 +9758,8 @@ void QgisApp::writeProject( QDomDocument &doc )
delete clonedRoot; delete clonedRoot;
doc.firstChildElement( "qgis" ).appendChild( oldLegendElem ); doc.firstChildElement( "qgis" ).appendChild( oldLegendElem );
QgsProject::instance()->writeEntry( "Legend", "filterByMap", (bool) layerTreeView()->layerTreeModel()->legendFilterByMap() );
projectChanged( doc ); projectChanged( doc );
} }

View File

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

View File

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

View File

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

View File

@ -85,6 +85,13 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
//! @note added in 2.6 //! @note added in 2.6
bool autoUpdateModel() const; 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 //setters and getters
void setTitle( const QString& t ); void setTitle( const QString& t );
QString title() const; QString title() const;
@ -176,6 +183,9 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
/**Sets mCompositionMap to 0 if the map is deleted*/ /**Sets mCompositionMap to 0 if the map is deleted*/
void invalidateCurrentMap(); void invalidateCurrentMap();
private slots:
void updateFilterByMap();
private: private:
QgsComposerLegend(); //forbidden QgsComposerLegend(); //forbidden
@ -190,6 +200,8 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
QgsLegendSettings mSettings; QgsLegendSettings mSettings;
const QgsComposerMap* mComposerMap; const QgsComposerMap* mComposerMap;
bool mLegendFilterByMap;
}; };
#endif #endif

View File

@ -176,6 +176,17 @@ void QgsComposerMap::draw( QPainter *painter, const QgsRectangle& extent, const
return; 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(); const QgsMapSettings& ms = mComposition->mapSettings();
QgsMapSettings jobMapSettings; QgsMapSettings jobMapSettings;
@ -218,12 +229,7 @@ void QgsComposerMap::draw( QPainter *painter, const QgsRectangle& extent, const
jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false ); jobMapSettings.setFlag( QgsMapSettings::DrawEditingInfo, false );
jobMapSettings.setFlag( QgsMapSettings::UseAdvancedEffects, mComposition->useAdvancedEffects() ); // respect the composition's useAdvancedEffects flag jobMapSettings.setFlag( QgsMapSettings::UseAdvancedEffects, mComposition->useAdvancedEffects() ); // respect the composition's useAdvancedEffects flag
// render return jobMapSettings;
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();
} }
void QgsComposerMap::cache( void ) void QgsComposerMap::cache( void )
@ -1964,7 +1970,7 @@ QString QgsComposerMap::displayName() const
return tr( "Map %1" ).arg( mId ); return tr( "Map %1" ).arg( mId );
} }
void QgsComposerMap::requestedExtent( QgsRectangle& extent ) void QgsComposerMap::requestedExtent( QgsRectangle& extent ) const
{ {
QgsRectangle newExtent = *currentMapExtent(); QgsRectangle newExtent = *currentMapExtent();
if ( mEvaluatedMapRotation == 0 ) if ( mEvaluatedMapRotation == 0 )

View File

@ -151,6 +151,10 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
/** \brief Create cache image */ /** \brief Create cache image */
void cache(); 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*/ /** \brief Get identification number*/
int id() const {return mId;} int id() const {return mId;}
@ -774,6 +778,10 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
Q_DECL_DEPRECATED void connectMapOverviewSignals(); 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: signals:
void extentChanged(); void extentChanged();
@ -893,8 +901,6 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
/** mapPolygon variant using a given extent */ /** mapPolygon variant using a given extent */
void mapPolygon( const QgsRectangle& extent, QPolygonF& poly ) const; 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 /**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 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*/ @param yShift in: shift in y direction (in item units), out: yShift in map units*/

View File

@ -22,6 +22,7 @@
#include <QTextStream> #include <QTextStream>
#include "qgsdataitem.h" #include "qgsdataitem.h"
#include "qgsmaphittest.h"
#include "qgsmaplayerlegend.h" #include "qgsmaplayerlegend.h"
#include "qgspluginlayer.h" #include "qgspluginlayer.h"
#include "qgsrasterlayer.h" #include "qgsrasterlayer.h"
@ -36,6 +37,9 @@ QgsLayerTreeModel::QgsLayerTreeModel( QgsLayerTreeGroup* rootNode, QObject *pare
, mFlags( ShowLegend | AllowLegendChangeState ) , mFlags( ShowLegend | AllowLegendChangeState )
, mAutoCollapseLegendNodesCount( -1 ) , mAutoCollapseLegendNodesCount( -1 )
, mLegendFilterByScale( 0 ) , mLegendFilterByScale( 0 )
, mLegendMapViewMupp( 0 )
, mLegendMapViewDpi( 0 )
, mLegendMapViewScale( 0 )
{ {
connectToRootNode(); connectToRootNode();
@ -66,10 +70,11 @@ QgsLayerTreeModelLegendNode* QgsLayerTreeModel::index2legendNode( const QModelIn
QModelIndex QgsLayerTreeModel::legendNode2index( QgsLayerTreeModelLegendNode* legendNode ) QModelIndex QgsLayerTreeModel::legendNode2index( QgsLayerTreeModelLegendNode* legendNode )
{ {
QModelIndex parentIndex = node2index( legendNode->parent() ); QModelIndex parentIndex = node2index( legendNode->layerNode() );
Q_ASSERT( parentIndex.isValid() ); Q_ASSERT( parentIndex.isValid() );
int row = mLegendNodes[legendNode->parent()].indexOf( legendNode ); int row = mLegendNodes[legendNode->layerNode()].indexOf( legendNode );
Q_ASSERT( row >= 0 ); 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 ); return index( row, 0, parentIndex );
} }
@ -145,7 +150,7 @@ QModelIndex QgsLayerTreeModel::parent( const QModelIndex &child ) const
{ {
QgsLayerTreeModelLegendNode* sym = index2legendNode( child ); QgsLayerTreeModelLegendNode* sym = index2legendNode( child );
Q_ASSERT( sym ); Q_ASSERT( sym );
parentNode = sym->parent(); parentNode = sym->layerNode();
} }
else else
parentNode = n->parent(); // must not be null 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 QgsLayerTreeLayer* QgsLayerTreeModel::layerNodeForSymbologyNode( const QModelIndex& index ) const
{ {
QgsLayerTreeModelLegendNode* symNode = index2legendNode( index ); QgsLayerTreeModelLegendNode* symNode = index2legendNode( index );
return symNode ? symNode->parent() : 0; return symNode ? symNode->layerNode() : 0;
} }
QList<QgsLayerTreeModelLegendNode*> QgsLayerTreeModel::layerLegendNodes( QgsLayerTreeLayer* nodeLayer ) QList<QgsLayerTreeModelLegendNode*> QgsLayerTreeModel::layerLegendNodes( QgsLayerTreeLayer* nodeLayer )
@ -571,6 +576,60 @@ void QgsLayerTreeModel::setLegendFilterByScale( double scaleDenominator )
refreshLayerLegend( nodeLayer ); 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 ) void QgsLayerTreeModel::nodeWillAddChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
{ {
Q_ASSERT( node ); Q_ASSERT( node );
@ -697,7 +756,8 @@ void QgsLayerTreeModel::legendNodeDataChanged()
return; return;
QModelIndex index = legendNode2index( legendNode ); 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*> QgsLayerTreeModel::filterLegendNodes( const QList<QgsLayerTreeModelLegendNode*>& nodes )
{ {
QList<QgsLayerTreeModelLegendNode*> filtered; 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; return filtered;
} }

View File

@ -24,7 +24,9 @@ class QgsLayerTreeNode;
class QgsLayerTreeGroup; class QgsLayerTreeGroup;
class QgsLayerTreeLayer; class QgsLayerTreeLayer;
class QgsLayerTreeModelLegendNode; class QgsLayerTreeModelLegendNode;
class QgsMapHitTest;
class QgsMapLayer; 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 //! Return legend node for given index. Returns null for invalid index
//! @note added in 2.6 //! @note added in 2.6
static QgsLayerTreeModelLegendNode* index2legendNode( const QModelIndex& index ); 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 //! @note added in 2.6
QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode ); QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode );
@ -144,6 +147,22 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
void setLegendFilterByScale( double scaleDenominator ); void setLegendFilterByScale( double scaleDenominator );
double legendFilterByScale() const { return mLegendFilterByScale; } 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) //! Return true if index represents a legend node (instead of layer node)
//! @deprecated use index2legendNode() //! @deprecated use index2legendNode()
Q_DECL_DEPRECATED bool isIndexSymbologyNode( const QModelIndex& index ) const; 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) //! scale denominator for filtering of legend nodes (<= 0 means no filtering)
double mLegendFilterByScale; double mLegendFilterByScale;
QScopedPointer<QgsMapSettings> mLegendFilterByMapSettings;
QScopedPointer<QgsMapHitTest> mLegendFilterByMapHitTest;
double mLegendMapViewMupp;
int mLegendMapViewDpi;
double mLegendMapViewScale;
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLayerTreeModel::Flags ) Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLayerTreeModel::Flags )

View File

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

View File

@ -20,10 +20,10 @@
#include <QObject> #include <QObject>
class QgsLayerTreeLayer; class QgsLayerTreeLayer;
class QgsLayerTreeModel;
class QgsLegendSettings; class QgsLegendSettings;
class QgsSymbolV2; class QgsSymbolV2;
/** /**
* The QgsLegendRendererItem class is abstract interface for legend items * The QgsLegendRendererItem class is abstract interface for legend items
* returned from QgsMapLayerLegend implementation. * returned from QgsMapLayerLegend implementation.
@ -41,11 +41,15 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
enum LegendNodeRoles 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 */ /** 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. */ /** Return item flags associated with the item. Default implementation returns Qt::ItemIsEnabled. */
virtual Qt::ItemFlags flags() const; 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; } 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 struct ItemContext
{ {
//! Painter //! Painter
@ -130,7 +138,7 @@ class CORE_EXPORT QgsLayerTreeModelLegendNode : public QObject
class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
{ {
public: public:
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item ); QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item, QObject* parent = 0 );
~QgsSymbolV2LegendNode(); ~QgsSymbolV2LegendNode();
virtual Qt::ItemFlags flags() const; 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 bool isScaleOK( double scale ) const { return mItem.isScaleOK( scale ); }
virtual void invalidateMapBasedData();
private: private:
void updateLabel(); void updateLabel();
@ -152,6 +162,7 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
QgsLegendSymbolItemV2 mItem; QgsLegendSymbolItemV2 mItem;
mutable QPixmap mPixmap; // cached symbol preview mutable QPixmap mPixmap; // cached symbol preview
QString mLabel; 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 ) ) 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] ); 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 ) ) 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 ) + double space = mSettings.style( QgsComposerLegendStyle::Symbol ).margin( QgsComposerLegendStyle::Right ) +
mSettings.style( QgsComposerLegendStyle::SymbolLabel ).margin( QgsComposerLegendStyle::Left ); mSettings.style( QgsComposerLegendStyle::SymbolLabel ).margin( QgsComposerLegendStyle::Left );
atom.nucleons[j].labelXOffset = maxSymbolWidth[key] + space; 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 //! @note added in 2.4
const QgsMapSettings& mapSettings(); 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: signals:
//! @deprecated in 2.4 - not emitted anymore //! @deprecated in 2.4 - not emitted anymore
@ -371,14 +379,6 @@ class CORE_EXPORT QgsMapRenderer : public QObject
//! adjust extent to fit the pixmap size //! adjust extent to fit the pixmap size
void adjustExtentToSize(); 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 //! indicates drawing in progress
static bool mDrawing; static bool mDrawing;
@ -437,7 +437,6 @@ class CORE_EXPORT QgsMapRenderer : public QObject
QgsMapSettings mMapSettings; QgsMapSettings mMapSettings;
QHash< QString, QgsLayerCoordinateTransform > mLayerCoordinateTransformInfo; QHash< QString, QgsLayerCoordinateTransform > mLayerCoordinateTransformInfo;
}; };
#endif #endif

View File

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

View File

@ -196,6 +196,40 @@ QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForValue( QVariant value )
} }
QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForFeature( QgsFeature& feature ) 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(); const QgsAttributes& attrs = feature.attributes();
QVariant value; QVariant value;
@ -220,33 +254,10 @@ QgsSymbolV2* QgsCategorizedSymbolRendererV2::symbolForFeature( QgsFeature& featu
return symbolForValue( QVariant( "" ) ); return symbolForValue( QVariant( "" ) );
} }
if ( !mRotation.data() && !mSizeScale.data() ) return symbol;
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;
} }
int QgsCategorizedSymbolRendererV2::categoryIndexForValue( QVariant val ) int QgsCategorizedSymbolRendererV2::categoryIndexForValue( QVariant val )
{ {
for ( int i = 0; i < mCategories.count(); i++ ) 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 ) | tempSymbol->setRenderHints(( mRotation.data() ? QgsSymbolV2::DataDefinedRotation : 0 ) |
( mSizeScale.data() ? QgsSymbolV2::DataDefinedSizeScale : 0 ) ); ( mSizeScale.data() ? QgsSymbolV2::DataDefinedSizeScale : 0 ) );
tempSymbol->startRender( context, &fields ); 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 ); it->symbol()->stopRender( context );
// cleanup mTempSymbols // cleanup mTempSymbols
QHash<QString, QgsSymbolV2*>::iterator it2 = mTempSymbols.begin(); QHash<QgsSymbolV2*, QgsSymbolV2*>::iterator it2 = mTempSymbols.begin();
for ( ; it2 != mTempSymbols.end(); ++it2 ) for ( ; it2 != mTempSymbols.end(); ++it2 )
{ {
it2.value()->stopRender( context ); it2.value()->stopRender( context );

View File

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

View File

@ -191,23 +191,7 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForValue( double value )
QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature ) QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature )
{ {
const QgsAttributes& attrs = feature.attributes(); QgsSymbolV2* symbol = originalSymbolForFeature( feature );
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() );
if ( symbol == NULL ) if ( symbol == NULL )
return NULL; return NULL;
@ -237,6 +221,27 @@ QgsSymbolV2* QgsGraduatedSymbolRendererV2::symbolForFeature( QgsFeature& feature
return tempSymbol; 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 ) void QgsGraduatedSymbolRendererV2::startRender( QgsRenderContext& context, const QgsFields& fields )
{ {
mCounting = context.rendererScale() == 0.0; mCounting = context.rendererScale() == 0.0;

View File

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

View File

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

View File

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

View File

@ -586,3 +586,11 @@ QgsSymbolV2List QgsFeatureRendererV2::symbolsForFeature( QgsFeature& feat )
if ( s ) lst.append( s ); if ( s ) lst.append( s );
return lst; 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; 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; virtual void startRender( QgsRenderContext& context, const QgsFields& fields ) = 0;
//! @deprecated since 2.4 - not using QgsVectorLayer directly anymore //! @deprecated since 2.4 - not using QgsVectorLayer directly anymore
@ -201,6 +209,11 @@ class CORE_EXPORT QgsFeatureRendererV2
//! @note added in 1.9 //! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat ); 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: protected:
QgsFeatureRendererV2( QString type ); QgsFeatureRendererV2( QString type );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -108,7 +108,8 @@ class CORE_EXPORT QgsSymbolLayerV2Utils
static void drawStippledBackround( QPainter* painter, QRect rect ); 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 ); static QPixmap colorRampPreviewPixmap( QgsVectorColorRampV2* ramp, QSize size );
/**Returns the maximum estimated bleed for the symbol */ /**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 // possibly a legend node
QgsLayerTreeModelLegendNode* legendNode = layerTreeModel()->index2legendNode( index ); QgsLayerTreeModelLegendNode* legendNode = layerTreeModel()->index2legendNode( index );
if ( legendNode ) if ( legendNode )
return legendNode->parent()->layer(); return legendNode->layerNode()->layer();
} }
return 0; return 0;
@ -234,7 +234,7 @@ QgsLayerTreeGroup* QgsLayerTreeView::currentGroupNode() const
if ( QgsLayerTreeModelLegendNode* legendNode = layerTreeModel()->index2legendNode( selectionModel()->currentIndex() ) ) if ( QgsLayerTreeModelLegendNode* legendNode = layerTreeModel()->index2legendNode( selectionModel()->currentIndex() ) )
{ {
QgsLayerTreeLayer* parent = legendNode->parent(); QgsLayerTreeLayer* parent = legendNode->layerNode();
if ( QgsLayerTree::isGroup( parent->parent() ) ) if ( QgsLayerTree::isGroup( parent->parent() ) )
return QgsLayerTree::toGroup( 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() QImage* QgsWMSServer::getLegendGraphics()
{ {
if ( !mConfigParser || !mMapRenderer ) if ( !mConfigParser || !mMapRenderer )
@ -542,6 +568,22 @@ QImage* QgsWMSServer::getLegendGraphics()
throw QgsMapServiceException( "FormatNotSpecified", "FORMAT is mandatory for GetLegendGraphic operation" ); 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; QStringList layersList, stylesList;
if ( readLayersAndStyles( layersList, stylesList ) != 0 ) if ( readLayersAndStyles( layersList, stylesList ) != 0 )
@ -622,6 +664,41 @@ QImage* QgsWMSServer::getLegendGraphics()
if ( scaleDenominator > 0 ) if ( scaleDenominator > 0 )
legendModel.setLegendFilterByScale( scaleDenominator ); 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 // find out DPI
QImage* tmpImage = createImage( 1, 1 ); QImage* tmpImage = createImage( 1, 1 );
if ( !tmpImage ) if ( !tmpImage )
@ -643,6 +720,13 @@ QImage* QgsWMSServer::getLegendGraphics()
// TODO: not available: layer font color // TODO: not available: layer font color
legendSettings.setFontColor( itemFontColor ); 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() ) if ( !rule.isEmpty() )
{ {
//create second image with the right dimensions //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, void QgsWMSServer::legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace,
double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight, double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight,
@ -961,7 +1108,7 @@ QImage* QgsWMSServer::printCompositionToImage( QgsComposition* c ) const
} }
#endif #endif
QImage* QgsWMSServer::getMap() QImage* QgsWMSServer::getMap( HitTest* hitTest )
{ {
if ( !checkMaximumWidthHeight() ) if ( !checkMaximumWidthHeight() )
{ {
@ -983,7 +1130,11 @@ QImage* QgsWMSServer::getMap()
applyOpacities( layersList, bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies ); applyOpacities( layersList, bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
mMapRenderer->render( &thePainter ); if ( hitTest )
runHitTest( &thePainter, *hitTest );
else
mMapRenderer->render( &thePainter );
if ( mConfigParser ) if ( mConfigParser )
{ {
//draw configuration format specific overlay items //draw configuration format specific overlay items

View File

@ -43,6 +43,7 @@ class QgsRectangle;
class QgsRenderContext; class QgsRenderContext;
class QgsVectorLayer; class QgsVectorLayer;
class QgsSymbol; class QgsSymbol;
class QgsSymbolV2;
class QColor; class QColor;
class QFile; class QFile;
class QFont; 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 /**Returns the map legend as an image (or a null pointer in case of error). The caller takes ownership
of the image object*/ of the image object*/
QImage* getLegendGraphics(); 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 /**Returns the map as an image (or a null pointer in case of error). The caller takes ownership
of the image object)*/ of the image object). If an instance to existing hit test structure is passed, instead of rendering
QImage* getMap(); 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 :-)*/ /**Returns an SLD file with the style of the requested layer. Exception is raised in case of troubles :-)*/
QDomDocument getStyle(); QDomDocument getStyle();
/**Returns an SLD file with the styles of the requested layers. Exception is raised in case of troubles :-)*/ /**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)*/ @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; 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*/ /**Read legend parameter from the request or from the first print composer in the project*/
void legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace, void legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace,
double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight, QFont& layerFont, QFont& itemFont, QColor& layerFontColor, QColor& itemFontColor ); double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight, QFont& layerFont, QFont& itemFont, QColor& layerFontColor, QColor& itemFontColor );

View File

@ -55,8 +55,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>375</width> <width>372</width>
<height>1319</height> <height>1327</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="mainLayout"> <layout class="QVBoxLayout" name="mainLayout">
@ -354,6 +354,26 @@
</property> </property>
</widget> </widget>
</item> </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> <item>
<spacer name="horizontalSpacer_4"> <spacer name="horizontalSpacer_4">
<property name="orientation"> <property name="orientation">
@ -845,6 +865,43 @@
<header>qgslayertreeview.h</header> <header>qgslayertreeview.h</header>
</customwidget> </customwidget>
</customwidgets> </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> <resources>
<include location="../../images/images.qrc"/> <include location="../../images/images.qrc"/>
</resources> </resources>