Skip to content

Commit

Permalink
Add support for contextual WMS legend graphics (hub #11859)
Browse files Browse the repository at this point in the history
Developed with funding from Regione Toscana SITA (CIG: Z410C90D94)
  • Loading branch information
Sandro Santilli committed Dec 22, 2014
1 parent 247c3dd commit bbed705
Show file tree
Hide file tree
Showing 12 changed files with 642 additions and 121 deletions.
4 changes: 3 additions & 1 deletion src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,9 @@ void QgisApp::layerTreeViewDoubleClicked( const QModelIndex& index )
QgsRasterLayer* rlayer = qobject_cast<QgsRasterLayer*>( layer );
if ( rlayer && rlayer->providerType() == "wms" )
{
QImage img = rlayer->dataProvider()->getLegendGraphic( mMapCanvas->scale() );
const QgsMapCanvas* canvas = mapCanvas();
QgsRectangle visibleExtent = canvas->mapSettings().outputExtentToLayerExtent( rlayer, canvas->extent() ); // in layer projection
QImage img = rlayer->dataProvider()->getLegendGraphic( mMapCanvas->scale(), false, &visibleExtent );

QFrame* popup = new QFrame();
popup->setAttribute( Qt::WA_DeleteOnClose );
Expand Down
2 changes: 1 addition & 1 deletion src/core/layertree/qgslayertreemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel

//! 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.
//! Ownership of map settings pointer does not change, a copy is made.
//! @note added in 2.6
void setLegendFilterByMap( const QgsMapSettings* settings );
const QgsMapSettings* legendFilterByMap() const { return mLegendFilterByMapSettings.data(); }
Expand Down
139 changes: 139 additions & 0 deletions src/core/layertree/qgslayertreemodellegendnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
Date : August 2014
Copyright : (C) 2014 by Martin Dobias
Email : wonder dot sk at gmail dot com
QgsWMSLegendNode : Sandro Santilli < strk at keybit dot net >
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
Expand Down Expand Up @@ -478,3 +481,139 @@ QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings& settings,
}
return settings.symbolSize();
}

// -------------------------------------------------------------------------

QgsWMSLegendNode::QgsWMSLegendNode( QgsLayerTreeLayer* nodeLayer, QObject* parent )
: QgsLayerTreeModelLegendNode( nodeLayer, parent )
, mValid( false )
{
}

const QImage& QgsWMSLegendNode::getLegendGraphic() const
{
if ( ! mValid && ! mFetcher )
{
// or maybe in presence of a downloader we should just delete it
// and start a new one ?

QgsRasterLayer* layer = qobject_cast<QgsRasterLayer*>( mLayerNode->layer() );
const QgsLayerTreeModel* mod = model();
if ( ! mod ) return mImage;
const QgsMapSettings* ms = mod->legendFilterByMap();

QgsRasterDataProvider* prov = layer->dataProvider();

Q_ASSERT( ! mFetcher );
mFetcher.reset( prov->getLegendGraphicFetcher( ms ) );
if ( mFetcher )
{
connect( mFetcher.data(), SIGNAL( finish( const QImage& ) ), this, SLOT( getLegendGraphicFinished( const QImage& ) ) );
connect( mFetcher.data(), SIGNAL( error( const QString& ) ), this, SLOT( getLegendGraphicErrored( const QString& ) ) );
connect( mFetcher.data(), SIGNAL( progress( qint64, qint64 ) ), this, SLOT( getLegendGraphicProgress( qint64, qint64 ) ) );
mFetcher->start();
} // else QgsDebugMsg("XXX No legend supported ?");

}

return mImage;
}

QVariant QgsWMSLegendNode::data( int role ) const
{
//QgsDebugMsg( QString("XXX data called with role %1 -- mImage size is %2x%3").arg(role).arg(mImage.width()).arg(mImage.height()) );

if ( role == Qt::DecorationRole )
{
return QPixmap::fromImage( getLegendGraphic() );
}
else if ( role == Qt::SizeHintRole )
{
return getLegendGraphic().size();
}
return QVariant();
}

QSizeF QgsWMSLegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
{
Q_UNUSED( itemHeight );

if ( ctx )
{
ctx->painter->drawImage( QRectF( ctx->point, settings.wmsLegendSize() ),
mImage,
QRectF( QPointF( 0, 0 ), mImage.size() ) );
}
return settings.wmsLegendSize();
}

/* private */
QImage QgsWMSLegendNode::renderMessage( const QString& msg ) const
{
const int fontHeight = 10;
const int margin = fontHeight / 2;
const int nlines = 1;

const int w = 512, h = fontHeight * nlines + margin * ( nlines + 1 );
QImage theImage( w, h, QImage::Format_ARGB32_Premultiplied );
QPainter painter;
painter.begin( &theImage );
painter.setPen( QColor( 255, 0, 0 ) );
painter.setFont( QFont( "Chicago", fontHeight ) );
painter.fillRect( 0, 0, w, h, QColor( 255, 255, 255 ) );
painter.drawText( 0, margin + fontHeight, msg );
//painter.drawText(0,2*(margin+fontHeight),QString("retrying in 5 seconds..."));
painter.end();

return theImage;
}

void QgsWMSLegendNode::getLegendGraphicProgress( qint64 cur, qint64 tot )
{
QString msg = QString( "Downloading... %1/%2" ).arg( cur ).arg( tot );
//QgsDebugMsg ( QString("XXX %1").arg(msg) );
mImage = renderMessage( msg );
emit dataChanged();
}

void QgsWMSLegendNode::getLegendGraphicErrored( const QString& msg )
{
if ( ! mFetcher ) return; // must be coming after finish

mImage = renderMessage( msg );
//QgsDebugMsg( QString("XXX emitting dataChanged after writing an image of %1x%2").arg(mImage.width()).arg(mImage.height()) );

emit dataChanged();

mFetcher.reset();

mValid = true; // we consider it valid anyway
// ... but remove validity after 5 seconds
//QTimer::singleShot(5000, this, SLOT(invalidateMapBasedData()));
}

void QgsWMSLegendNode::getLegendGraphicFinished( const QImage& theImage )
{
if ( ! mFetcher ) return; // must be coming after error

//QgsDebugMsg( QString("XXX legend graphic finished, image is %1x%2").arg(theImage.width()).arg(theImage.height()) );
if ( ! theImage.isNull() )
{
if ( theImage != mImage )
{
mImage = theImage;
//QgsDebugMsg( QString("XXX emitting dataChanged") );
emit dataChanged();
}
mValid = true; // only if not null I guess
}
mFetcher.reset();
}

void QgsWMSLegendNode::invalidateMapBasedData()
{
//QgsDebugMsg( QString("XXX invalidateMapBasedData called") );
// TODO: do this only if this extent != prev extent ?
mValid = false;
emit dataChanged();
}
46 changes: 46 additions & 0 deletions src/core/layertree/qgslayertreemodellegendnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
Date : August 2014
Copyright : (C) 2014 by Martin Dobias
Email : wonder dot sk at gmail dot com
QgsWMSLegendNode : Sandro Santilli < strk at keybit dot net >
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
Expand All @@ -19,9 +22,12 @@
#include <QIcon>
#include <QObject>

#include "qgsrasterdataprovider.h" // for QgsImageFetcher dtor visibility

class QgsLayerTreeLayer;
class QgsLayerTreeModel;
class QgsLegendSettings;
class QgsMapSettings;
class QgsSymbolV2;

/**
Expand Down Expand Up @@ -222,4 +228,44 @@ class CORE_EXPORT QgsRasterSymbolLegendNode : public QgsLayerTreeModelLegendNode
QString mLabel;
};

class QgsImageFetcher;

/**
* Implementation of legend node interface for displaying WMS legend entries
*
* @note added in 2.8
*/
class CORE_EXPORT QgsWMSLegendNode : public QgsLayerTreeModelLegendNode
{
Q_OBJECT

public:
QgsWMSLegendNode( QgsLayerTreeLayer* nodeLayer, QObject* parent = 0 );

virtual QVariant data( int role ) const;

virtual QSizeF drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const;

virtual void invalidateMapBasedData();

private slots:

void getLegendGraphicFinished( const QImage& );
void getLegendGraphicErrored( const QString& );
void getLegendGraphicProgress( qint64, qint64 );

private:

// Lazily initializes mImage
const QImage& getLegendGraphic() const;

QImage renderMessage( const QString& msg ) const;

QImage mImage;

bool mValid;

mutable QScopedPointer<QgsImageFetcher> mFetcher;
};

#endif // QGSLAYERTREEMODELLEGENDNODE_H
7 changes: 1 addition & 6 deletions src/core/qgsmaplayerlegend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,7 @@ QList<QgsLayerTreeModelLegendNode*> QgsDefaultRasterLayerLegend::createLayerTree
// temporary solution for WMS. Ideally should be done with a delegate.
if ( mLayer->providerType() == "wms" )
{
QImage legendGraphic = mLayer->dataProvider()->getLegendGraphic();
if ( !legendGraphic.isNull() )
{
QgsDebugMsg( QString( "downloaded legend with dimension width:" ) + QString::number( legendGraphic.width() ) + QString( " and Height:" ) + QString::number( legendGraphic.height() ) );
nodes << new QgsImageLegendNode( nodeLayer, legendGraphic );
}
nodes << new QgsWMSLegendNode( nodeLayer );
}

QgsLegendColorList rasterItemList = mLayer->legendSymbologyItems();
Expand Down
57 changes: 55 additions & 2 deletions src/core/raster/qgsrasterdataprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
Date : Mar 11, 2005
Copyright : (C) 2005 by Brendan Morley
email : morb at ozemail dot com dot au
async legend fetcher : Sandro Santilli < strk at keybit dot net >
***************************************************************************/

/***************************************************************************
Expand Down Expand Up @@ -46,6 +49,31 @@ class QByteArray;

class QgsPoint;
class QgsRasterIdentifyResult;
class QgsMapSettings;

/**
* \class Handles asynchronous download of images
*
* \note added in 2.8
*/
class QgsImageFetcher : public QObject
{
Q_OBJECT
public:

QgsImageFetcher() {};
virtual ~QgsImageFetcher( ) {}

// Make sure to connect to "finish" and "error" before starting
virtual void start() = 0;

signals:

void finish( const QImage& legend );
void progress( qint64 received, qint64 total );
void error( const QString& msg );
};


/** \ingroup core
* Base class for raster data providers.
Expand Down Expand Up @@ -201,14 +229,39 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
}

/** \brief Returns the legend rendered as pixmap
* useful for that layer that need to get legend layer remotly as WMS */
virtual QImage getLegendGraphic( double scale = 0, bool forceRefresh = false )
*
* useful for that layer that need to get legend layer remotely as WMS
* \param visibleExtent Visible extent for providers supporting
* contextual legends, in layer CRS
* \note visibleExtent parameter added in 2.8
*/
virtual QImage getLegendGraphic( double scale = 0, bool forceRefresh = false, const QgsRectangle * visibleExtent = 0 )
{
Q_UNUSED( scale );
Q_UNUSED( forceRefresh );
Q_UNUSED( visibleExtent );
return QImage();
}

/**
* \class Get an image downloader for the raster legend
*
* \param mapSettings map settings for legend providers supporting
* contextual legends.
*
* \return a download handler or null if the provider does not support
* legend at all. Ownership of the returned object is transferred
* to caller.
*
* \note added in 2.8
*
*/
virtual QgsImageFetcher* getLegendGraphicFetcher( const QgsMapSettings* mapSettings )
{
Q_UNUSED( mapSettings );
return 0;
}

/** \brief Create pyramid overviews */
virtual QString buildPyramids( const QList<QgsRasterPyramid> & thePyramidList,
const QString & theResamplingMethod = "NEAREST",
Expand Down
3 changes: 3 additions & 0 deletions src/providers/wms/qgswmscapabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ bool QgsWmsSettings::parseUri( QString uriString )

mCrsId = uri.param( "crs" );

mEnableContextualLegend = uri.param( "contextualWMSLegend" ).toInt();
QgsDebugMsg( QString( "Contextual legend: %1" ).arg( mEnableContextualLegend ) );

mFeatureCount = uri.param( "featureCount" ).toInt(); // default to 0

return true;
Expand Down
2 changes: 2 additions & 0 deletions src/providers/wms/qgswmscapabilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ class QgsWmsSettings

QString mCrsId;

bool mEnableContextualLegend;

friend class QgsWmsProvider;
};

Expand Down
Loading

0 comments on commit bbed705

Please sign in to comment.