Skip to content

Commit

Permalink
[FEATURE] Legend filtering based on map content (in main window, comp…
Browse files Browse the repository at this point in the history
…oser, 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.
  • Loading branch information
wonder-sk committed Sep 25, 2014
1 parent a111c85 commit e37a5ad
Show file tree
Hide file tree
Showing 48 changed files with 893 additions and 124 deletions.
7 changes: 7 additions & 0 deletions python/core/composer/qgscomposerlegend.sip
Expand Up @@ -58,6 +58,13 @@ class QgsComposerLegend : QgsComposerItem
//! @note added in 2.6
bool autoUpdateModel() const;

//! Set whether legend items should be filtered to show just the ones visible in the associated map
//! @note added in 2.6
void setLegendFilterByMapEnabled( bool enabled );
//! Find out whether legend items are filtered to show just the ones visible in the associated map
//! @note added in 2.6
bool legendFilterByMapEnabled() const;

//setters and getters
void setTitle( const QString& t );
QString title() const;
Expand Down
8 changes: 8 additions & 0 deletions python/core/composer/qgscomposermap.sip
Expand Up @@ -111,6 +111,10 @@ class QgsComposerMap : QgsComposerItem
/** \brief Create cache image */
void cache();

/** Return map settings that would be used for drawing of the map
* @note added in 2.6 */
QgsMapSettings mapSettings( const QgsRectangle& extent, const QSizeF& size, int dpi ) const;

/** \brief Get identification number*/
int id() const;

Expand Down Expand Up @@ -737,6 +741,10 @@ class QgsComposerMap : QgsComposerItem

void connectMapOverviewSignals() /Deprecated/;

/**Calculates the extent to request and the yShift of the top-left point in case of rotation.
* @note added in 2.6 */
void requestedExtent( QgsRectangle& extent ) const;

signals:
void extentChanged();

Expand Down
19 changes: 18 additions & 1 deletion python/core/layertree/qgslayertreemodel.sip
Expand Up @@ -85,7 +85,8 @@ class QgsLayerTreeModel : QAbstractItemModel
//! Return legend node for given index. Returns null for invalid index
//! @note added in 2.6
static QgsLayerTreeModelLegendNode* index2legendNode( const QModelIndex& index );
//! Return index for a given legend node. If the legend node does not belong to the layer tree, the result is undefined
//! Return index for a given legend node. If the legend node does not belong to the layer tree, the result is undefined.
//! If the legend node is belongs to the tree but it is filtered out, invalid model index is returned.
//! @note added in 2.6
QModelIndex legendNode2index( QgsLayerTreeModelLegendNode* legendNode );

Expand Down Expand Up @@ -124,6 +125,22 @@ class QgsLayerTreeModel : QAbstractItemModel
void setLegendFilterByScale( double scaleDenominator );
double legendFilterByScale() const;

//! Force only display of legend nodes which are valid for given map settings.
//! Setting null pointer or invalid map settings will disable the functionality.
//! Ownership of map settings pointer does not change.
//! @note added in 2.6
void setLegendFilterByMap( const QgsMapSettings* settings );
const QgsMapSettings* legendFilterByMap() const;

//! Give the layer tree model hints about the currently associated map view
//! so that legend nodes that use map units can be scaled currectly
//! @note added in 2.6
void setLegendMapViewData( double mapUnitsPerPixel, int dpi, double scale );
//! Get hints about map view - to be used in legend nodes. Arguments that are not null will receive values.
//! If there are no valid map view data (from previous call to setLegendMapViewData()), returned values are zeros.
//! @note added in 2.6
void legendMapViewData( double *mapUnitsPerPixel /Out/, int *dpi /Out/, double *scale /Out/ );

//! Return true if index represents a legend node (instead of layer node)
//! @deprecated use index2legendNode()
bool isIndexSymbologyNode( const QModelIndex& index ) const /Deprecated/;
Expand Down
16 changes: 13 additions & 3 deletions python/core/layertree/qgslayertreemodellegendnode.sip
Expand Up @@ -19,11 +19,15 @@ class QgsLayerTreeModelLegendNode : QObject

enum LegendNodeRoles
{
RuleKeyRole
RuleKeyRole, //!< rule key of the node (QString)
SymbolV2LegacyRuleKeyRole //!< for QgsSymbolV2LegendNode only - legacy rule key (void ptr, to be cast to QgsSymbolV2 ptr)
};

/** Return pointer to the parent layer node */
QgsLayerTreeLayer* parent() const;
QgsLayerTreeLayer* layerNode() const;

/** Return pointer to model owning this legend node */
QgsLayerTreeModel* model() const;

/** Return item flags associated with the item. Default implementation returns Qt::ItemIsEnabled. */
virtual Qt::ItemFlags flags() const;
Expand All @@ -42,6 +46,10 @@ class QgsLayerTreeModelLegendNode : QObject

virtual bool isScaleOK( double scale ) const;

/** Notification from model that information from associated map view has changed.
* Default implementation does nothing. */
virtual void invalidateMapBasedData();

struct ItemContext
{
//! Painter
Expand Down Expand Up @@ -109,7 +117,7 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
#include <qgslayertreemodellegendnode.h>
%End
public:
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item );
QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, const QgsLegendSymbolItemV2& item, QObject* parent /TransferThis/ = 0 );
~QgsSymbolV2LegendNode();

virtual Qt::ItemFlags flags() const;
Expand All @@ -123,6 +131,8 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
void setUserLabel( const QString& userLabel );

virtual bool isScaleOK( double scale ) const;

virtual void invalidateMapBasedData();
};


Expand Down
15 changes: 8 additions & 7 deletions python/core/qgsmaprenderer.sip
Expand Up @@ -287,6 +287,14 @@ class QgsMapRenderer : QObject
//! @note added in 2.4
const QgsMapSettings& mapSettings();

/** Convenience function to project an extent into the layer source
* CRS, but also split it into two extents if it crosses
* the +/- 180 degree line. Modifies the given extent to be in the
* source CRS coordinates, and if it was split, returns true, and
* also sets the contents of the r2 parameter
*/
bool splitLayersExtent( QgsMapLayer* layer, QgsRectangle& extent /In,Out/, QgsRectangle& r2 /Out/ );

signals:

//! @deprecated in 2.4 - not emitted anymore
Expand Down Expand Up @@ -331,11 +339,4 @@ class QgsMapRenderer : QObject
//! adjust extent to fit the pixmap size
void adjustExtentToSize();

/** Convenience function to project an extent into the layer source
* CRS, but also split it into two extents if it crosses
* the +/- 180 degree line. Modifies the given extent to be in the
* source CRS coordinates, and if it was split, returns true, and
* also sets the contents of the r2 parameter
*/
bool splitLayersExtent( QgsMapLayer* layer, QgsRectangle& extent, QgsRectangle& r2 );
};
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgscategorizedsymbolrendererv2.sip
Expand Up @@ -47,6 +47,8 @@ class QgsCategorizedSymbolRendererV2 : QgsFeatureRendererV2

virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );

virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );

virtual void startRender( QgsRenderContext& context, const QgsFields& fields );

virtual void stopRender( QgsRenderContext& context );
Expand Down
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgsgraduatedsymbolrendererv2.sip
Expand Up @@ -51,6 +51,8 @@ class QgsGraduatedSymbolRendererV2 : QgsFeatureRendererV2

virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );

virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );

virtual void startRender( QgsRenderContext& context, const QgsFields& fields );

virtual void stopRender( QgsRenderContext& context );
Expand Down
4 changes: 4 additions & 0 deletions python/core/symbology-ng/qgsinvertedpolygonrenderer.sip
Expand Up @@ -45,8 +45,12 @@ class QgsInvertedPolygonRenderer : QgsFeatureRendererV2
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );
/** Proxy that will call this method on the embedded renderer. */
virtual QgsLegendSymbologyList legendSymbologyItems( QSize iconSize );
/** Proxy that will call this method on the embedded renderer.
@note not available in python bindings
Expand Down
13 changes: 13 additions & 0 deletions python/core/symbology-ng/qgsrendererv2.sip
Expand Up @@ -60,6 +60,14 @@ class QgsFeatureRendererV2
*/
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature ) = 0;

/**
* Return symbol for feature. The difference compared to symbolForFeature() is that it returns original
* symbol which can be used as an identifier for renderer's rule - the former may return a temporary replacement
* of a symbol for use in rendering.
* @note added in 2.6
*/
virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );

virtual void startRender( QgsRenderContext& context, const QgsFields& fields ) = 0;

//! @deprecated since 2.4 - not using QgsVectorLayer directly anymore
Expand Down Expand Up @@ -175,6 +183,11 @@ class QgsFeatureRendererV2
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );

//! Equivalent of originalSymbolsForFeature() call
//! extended to support renderers that may use more symbols per feature - similar to symbolsForFeature()
//! @note added in 2.6
virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );

protected:
QgsFeatureRendererV2( QString type );

Expand Down
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgsrulebasedrendererv2.sip
Expand Up @@ -216,6 +216,8 @@ class QgsRuleBasedRendererV2 : QgsFeatureRendererV2
//! @note added in 1.9
virtual QgsSymbolV2List symbolsForFeature( QgsFeature& feat );

virtual QgsSymbolV2List originalSymbolsForFeature( QgsFeature& feat );

//! returns bitwise OR-ed capabilities of the renderer
//! \note added in 2.0
virtual int capabilities();
Expand Down
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgssinglesymbolrendererv2.sip
Expand Up @@ -11,6 +11,8 @@ class QgsSingleSymbolRendererV2 : QgsFeatureRendererV2

virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature );

virtual QgsSymbolV2* originalSymbolForFeature( QgsFeature& feature );

virtual void startRender( QgsRenderContext& context, const QgsFields& fields );

virtual void stopRender( QgsRenderContext& context );
Expand Down
3 changes: 2 additions & 1 deletion python/core/symbology-ng/qgssymbollayerv2utils.sip
Expand Up @@ -68,7 +68,8 @@ class QgsSymbolLayerV2Utils

static void drawStippledBackround( QPainter* painter, QRect rect );

static QPixmap symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size );
//! @note customContext parameter added in 2.6
static QPixmap symbolPreviewPixmap( QgsSymbolV2* symbol, QSize size, QgsRenderContext* customContext = 0 );
static QPixmap colorRampPreviewPixmap( QgsVectorColorRampV2* ramp, QSize size );

/**Returns the maximum estimated bleed for the symbol */
Expand Down
29 changes: 20 additions & 9 deletions src/app/composer/qgscomposerlegendwidget.cpp
Expand Up @@ -145,6 +145,7 @@ void QgsComposerLegendWidget::setGuiElements()
blockAllSignals( true );
mTitleLineEdit->setText( mLegend->title() );
mTitleAlignCombo->setCurrentIndex( alignment );
mFilterByMapToolButton->setChecked( mLegend->legendFilterByMapEnabled() );
mColumnCountSpinBox->setValue( mLegend->columnCount() );
mSplitLayerCheckBox->setChecked( mLegend->splitLayer() );
mEqualColumnWidthCheckBox->setChecked( mLegend->equalColumnWidth() );
Expand Down Expand Up @@ -531,8 +532,8 @@ void QgsComposerLegendWidget::on_mMoveDownToolButton_clicked()
}
else // legend node
{
_moveLegendNode( legendNode->parent(), index.row(), 1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->parent() );
_moveLegendNode( legendNode->layerNode(), index.row(), 1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
}

mItemTreeView->setCurrentIndex( mItemTreeView->layerTreeModel()->index( index.row() + 1, 0, parentIndex ) );
Expand Down Expand Up @@ -568,8 +569,8 @@ void QgsComposerLegendWidget::on_mMoveUpToolButton_clicked()
}
else // legend node
{
_moveLegendNode( legendNode->parent(), index.row(), -1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->parent() );
_moveLegendNode( legendNode->layerNode(), index.row(), -1 );
mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
}

mItemTreeView->setCurrentIndex( mItemTreeView->layerTreeModel()->index( index.row() - 1, 0, parentIndex ) );
Expand Down Expand Up @@ -689,7 +690,7 @@ void QgsComposerLegendWidget::on_mRemoveToolButton_clicked()
{
if ( QgsLayerTreeModelLegendNode* legendNode = mItemTreeView->layerTreeModel()->index2legendNode( index ) )
{
QgsLayerTreeLayer* nodeLayer = legendNode->parent();
QgsLayerTreeLayer* nodeLayer = legendNode->layerNode();
nodesWithRemoval[nodeLayer].append( index.row() );
}
}
Expand Down Expand Up @@ -773,10 +774,10 @@ void QgsComposerLegendWidget::on_mEditPushButton_clicked()
}
else if ( legendNode )
{
QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->parent() );
QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->layerNode() );
int originalIndex = ( idx.row() >= 0 && idx.row() < order.count() ? order[idx.row()] : -1 );
QgsMapLayerLegendUtils::setLegendNodeUserLabel( legendNode->parent(), originalIndex, newText );
model->refreshLayerLegend( legendNode->parent() );
QgsMapLayerLegendUtils::setLegendNodeUserLabel( legendNode->layerNode(), originalIndex, newText );
model->refreshLayerLegend( legendNode->layerNode() );
}

mLegend->adjustBoxSize();
Expand Down Expand Up @@ -806,7 +807,7 @@ void QgsComposerLegendWidget::resetLayerNodeToDefaults()
}
if ( QgsLayerTreeModelLegendNode* legendNode = mItemTreeView->layerTreeModel()->index2legendNode( currentIndex ) )
{
nodeLayer = legendNode->parent();
nodeLayer = legendNode->layerNode();
}

if ( !nodeLayer )
Expand Down Expand Up @@ -853,6 +854,15 @@ void QgsComposerLegendWidget::on_mCountToolButton_clicked( bool checked )
mLegend->endCommand();
}

void QgsComposerLegendWidget::on_mFilterByMapToolButton_clicked( bool checked )
{
mLegend->beginCommand( tr( "Legend updated" ) );
mLegend->setLegendFilterByMapEnabled( checked );
mLegend->update();
mLegend->adjustBoxSize();
mLegend->endCommand();
}

void QgsComposerLegendWidget::on_mUpdateAllPushButton_clicked()
{
updateLegend();
Expand Down Expand Up @@ -891,6 +901,7 @@ void QgsComposerLegendWidget::blockAllSignals( bool b )
mItemTreeView->blockSignals( b );
mCheckBoxAutoUpdate->blockSignals( b );
mMapComboBox->blockSignals( b );
mFilterByMapToolButton->blockSignals( b );
mColumnCountSpinBox->blockSignals( b );
mSplitLayerCheckBox->blockSignals( b );
mEqualColumnWidthCheckBox->blockSignals( b );
Expand Down
1 change: 1 addition & 0 deletions src/app/composer/qgscomposerlegendwidget.h
Expand Up @@ -76,6 +76,7 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
void on_mAddToolButton_clicked();
void on_mEditPushButton_clicked();
void on_mCountToolButton_clicked( bool checked );
void on_mFilterByMapToolButton_clicked( bool checked );
void resetLayerNodeToDefaults();
void on_mUpdateAllPushButton_clicked();
void on_mAddGroupToolButton_clicked();
Expand Down

2 comments on commit e37a5ad

@NathanW2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooooohhhh fancy!

@SrNetoChan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet!!! Again, it seems like if you guys can read my mind, and guess what my wishes are!

Please sign in to comment.