Skip to content

Commit 49d864f

Browse files
committed
Merge pull request #1735 from strk/wmslegend
Add support for map-contextual WMS legend
2 parents c0b94cf + bbed705 commit 49d864f

12 files changed

+642
-121
lines changed

src/app/qgisapp.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,9 @@ void QgisApp::layerTreeViewDoubleClicked( const QModelIndex& index )
394394
QgsRasterLayer* rlayer = qobject_cast<QgsRasterLayer*>( layer );
395395
if ( rlayer && rlayer->providerType() == "wms" )
396396
{
397-
QImage img = rlayer->dataProvider()->getLegendGraphic( mMapCanvas->scale() );
397+
const QgsMapCanvas* canvas = mapCanvas();
398+
QgsRectangle visibleExtent = canvas->mapSettings().outputExtentToLayerExtent( rlayer, canvas->extent() ); // in layer projection
399+
QImage img = rlayer->dataProvider()->getLegendGraphic( mMapCanvas->scale(), false, &visibleExtent );
398400

399401
QFrame* popup = new QFrame();
400402
popup->setAttribute( Qt::WA_DeleteOnClose );

src/core/layertree/qgslayertreemodel.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
149149

150150
//! Force only display of legend nodes which are valid for given map settings.
151151
//! Setting null pointer or invalid map settings will disable the functionality.
152-
//! Ownership of map settings pointer does not change.
152+
//! Ownership of map settings pointer does not change, a copy is made.
153153
//! @note added in 2.6
154154
void setLegendFilterByMap( const QgsMapSettings* settings );
155155
const QgsMapSettings* legendFilterByMap() const { return mLegendFilterByMapSettings.data(); }

src/core/layertree/qgslayertreemodellegendnode.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
Date : August 2014
55
Copyright : (C) 2014 by Martin Dobias
66
Email : wonder dot sk at gmail dot com
7+
8+
QgsWMSLegendNode : Sandro Santilli < strk at keybit dot net >
9+
710
***************************************************************************
811
* *
912
* This program is free software; you can redistribute it and/or modify *
@@ -478,3 +481,139 @@ QSizeF QgsRasterSymbolLegendNode::drawSymbol( const QgsLegendSettings& settings,
478481
}
479482
return settings.symbolSize();
480483
}
484+
485+
// -------------------------------------------------------------------------
486+
487+
QgsWMSLegendNode::QgsWMSLegendNode( QgsLayerTreeLayer* nodeLayer, QObject* parent )
488+
: QgsLayerTreeModelLegendNode( nodeLayer, parent )
489+
, mValid( false )
490+
{
491+
}
492+
493+
const QImage& QgsWMSLegendNode::getLegendGraphic() const
494+
{
495+
if ( ! mValid && ! mFetcher )
496+
{
497+
// or maybe in presence of a downloader we should just delete it
498+
// and start a new one ?
499+
500+
QgsRasterLayer* layer = qobject_cast<QgsRasterLayer*>( mLayerNode->layer() );
501+
const QgsLayerTreeModel* mod = model();
502+
if ( ! mod ) return mImage;
503+
const QgsMapSettings* ms = mod->legendFilterByMap();
504+
505+
QgsRasterDataProvider* prov = layer->dataProvider();
506+
507+
Q_ASSERT( ! mFetcher );
508+
mFetcher.reset( prov->getLegendGraphicFetcher( ms ) );
509+
if ( mFetcher )
510+
{
511+
connect( mFetcher.data(), SIGNAL( finish( const QImage& ) ), this, SLOT( getLegendGraphicFinished( const QImage& ) ) );
512+
connect( mFetcher.data(), SIGNAL( error( const QString& ) ), this, SLOT( getLegendGraphicErrored( const QString& ) ) );
513+
connect( mFetcher.data(), SIGNAL( progress( qint64, qint64 ) ), this, SLOT( getLegendGraphicProgress( qint64, qint64 ) ) );
514+
mFetcher->start();
515+
} // else QgsDebugMsg("XXX No legend supported ?");
516+
517+
}
518+
519+
return mImage;
520+
}
521+
522+
QVariant QgsWMSLegendNode::data( int role ) const
523+
{
524+
//QgsDebugMsg( QString("XXX data called with role %1 -- mImage size is %2x%3").arg(role).arg(mImage.width()).arg(mImage.height()) );
525+
526+
if ( role == Qt::DecorationRole )
527+
{
528+
return QPixmap::fromImage( getLegendGraphic() );
529+
}
530+
else if ( role == Qt::SizeHintRole )
531+
{
532+
return getLegendGraphic().size();
533+
}
534+
return QVariant();
535+
}
536+
537+
QSizeF QgsWMSLegendNode::drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const
538+
{
539+
Q_UNUSED( itemHeight );
540+
541+
if ( ctx )
542+
{
543+
ctx->painter->drawImage( QRectF( ctx->point, settings.wmsLegendSize() ),
544+
mImage,
545+
QRectF( QPointF( 0, 0 ), mImage.size() ) );
546+
}
547+
return settings.wmsLegendSize();
548+
}
549+
550+
/* private */
551+
QImage QgsWMSLegendNode::renderMessage( const QString& msg ) const
552+
{
553+
const int fontHeight = 10;
554+
const int margin = fontHeight / 2;
555+
const int nlines = 1;
556+
557+
const int w = 512, h = fontHeight * nlines + margin * ( nlines + 1 );
558+
QImage theImage( w, h, QImage::Format_ARGB32_Premultiplied );
559+
QPainter painter;
560+
painter.begin( &theImage );
561+
painter.setPen( QColor( 255, 0, 0 ) );
562+
painter.setFont( QFont( "Chicago", fontHeight ) );
563+
painter.fillRect( 0, 0, w, h, QColor( 255, 255, 255 ) );
564+
painter.drawText( 0, margin + fontHeight, msg );
565+
//painter.drawText(0,2*(margin+fontHeight),QString("retrying in 5 seconds..."));
566+
painter.end();
567+
568+
return theImage;
569+
}
570+
571+
void QgsWMSLegendNode::getLegendGraphicProgress( qint64 cur, qint64 tot )
572+
{
573+
QString msg = QString( "Downloading... %1/%2" ).arg( cur ).arg( tot );
574+
//QgsDebugMsg ( QString("XXX %1").arg(msg) );
575+
mImage = renderMessage( msg );
576+
emit dataChanged();
577+
}
578+
579+
void QgsWMSLegendNode::getLegendGraphicErrored( const QString& msg )
580+
{
581+
if ( ! mFetcher ) return; // must be coming after finish
582+
583+
mImage = renderMessage( msg );
584+
//QgsDebugMsg( QString("XXX emitting dataChanged after writing an image of %1x%2").arg(mImage.width()).arg(mImage.height()) );
585+
586+
emit dataChanged();
587+
588+
mFetcher.reset();
589+
590+
mValid = true; // we consider it valid anyway
591+
// ... but remove validity after 5 seconds
592+
//QTimer::singleShot(5000, this, SLOT(invalidateMapBasedData()));
593+
}
594+
595+
void QgsWMSLegendNode::getLegendGraphicFinished( const QImage& theImage )
596+
{
597+
if ( ! mFetcher ) return; // must be coming after error
598+
599+
//QgsDebugMsg( QString("XXX legend graphic finished, image is %1x%2").arg(theImage.width()).arg(theImage.height()) );
600+
if ( ! theImage.isNull() )
601+
{
602+
if ( theImage != mImage )
603+
{
604+
mImage = theImage;
605+
//QgsDebugMsg( QString("XXX emitting dataChanged") );
606+
emit dataChanged();
607+
}
608+
mValid = true; // only if not null I guess
609+
}
610+
mFetcher.reset();
611+
}
612+
613+
void QgsWMSLegendNode::invalidateMapBasedData()
614+
{
615+
//QgsDebugMsg( QString("XXX invalidateMapBasedData called") );
616+
// TODO: do this only if this extent != prev extent ?
617+
mValid = false;
618+
emit dataChanged();
619+
}

src/core/layertree/qgslayertreemodellegendnode.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
Date : August 2014
55
Copyright : (C) 2014 by Martin Dobias
66
Email : wonder dot sk at gmail dot com
7+
8+
QgsWMSLegendNode : Sandro Santilli < strk at keybit dot net >
9+
710
***************************************************************************
811
* *
912
* This program is free software; you can redistribute it and/or modify *
@@ -19,9 +22,12 @@
1922
#include <QIcon>
2023
#include <QObject>
2124

25+
#include "qgsrasterdataprovider.h" // for QgsImageFetcher dtor visibility
26+
2227
class QgsLayerTreeLayer;
2328
class QgsLayerTreeModel;
2429
class QgsLegendSettings;
30+
class QgsMapSettings;
2531
class QgsSymbolV2;
2632

2733
/**
@@ -222,4 +228,44 @@ class CORE_EXPORT QgsRasterSymbolLegendNode : public QgsLayerTreeModelLegendNode
222228
QString mLabel;
223229
};
224230

231+
class QgsImageFetcher;
232+
233+
/**
234+
* Implementation of legend node interface for displaying WMS legend entries
235+
*
236+
* @note added in 2.8
237+
*/
238+
class CORE_EXPORT QgsWMSLegendNode : public QgsLayerTreeModelLegendNode
239+
{
240+
Q_OBJECT
241+
242+
public:
243+
QgsWMSLegendNode( QgsLayerTreeLayer* nodeLayer, QObject* parent = 0 );
244+
245+
virtual QVariant data( int role ) const;
246+
247+
virtual QSizeF drawSymbol( const QgsLegendSettings& settings, ItemContext* ctx, double itemHeight ) const;
248+
249+
virtual void invalidateMapBasedData();
250+
251+
private slots:
252+
253+
void getLegendGraphicFinished( const QImage& );
254+
void getLegendGraphicErrored( const QString& );
255+
void getLegendGraphicProgress( qint64, qint64 );
256+
257+
private:
258+
259+
// Lazily initializes mImage
260+
const QImage& getLegendGraphic() const;
261+
262+
QImage renderMessage( const QString& msg ) const;
263+
264+
QImage mImage;
265+
266+
bool mValid;
267+
268+
mutable QScopedPointer<QgsImageFetcher> mFetcher;
269+
};
270+
225271
#endif // QGSLAYERTREEMODELLEGENDNODE_H

src/core/qgsmaplayerlegend.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,7 @@ QList<QgsLayerTreeModelLegendNode*> QgsDefaultRasterLayerLegend::createLayerTree
227227
// temporary solution for WMS. Ideally should be done with a delegate.
228228
if ( mLayer->providerType() == "wms" )
229229
{
230-
QImage legendGraphic = mLayer->dataProvider()->getLegendGraphic();
231-
if ( !legendGraphic.isNull() )
232-
{
233-
QgsDebugMsg( QString( "downloaded legend with dimension width:" ) + QString::number( legendGraphic.width() ) + QString( " and Height:" ) + QString::number( legendGraphic.height() ) );
234-
nodes << new QgsImageLegendNode( nodeLayer, legendGraphic );
235-
}
230+
nodes << new QgsWMSLegendNode( nodeLayer );
236231
}
237232

238233
QgsLegendColorList rasterItemList = mLayer->legendSymbologyItems();

src/core/raster/qgsrasterdataprovider.h

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
Date : Mar 11, 2005
55
Copyright : (C) 2005 by Brendan Morley
66
email : morb at ozemail dot com dot au
7+
8+
async legend fetcher : Sandro Santilli < strk at keybit dot net >
9+
710
***************************************************************************/
811

912
/***************************************************************************
@@ -46,6 +49,31 @@ class QByteArray;
4649

4750
class QgsPoint;
4851
class QgsRasterIdentifyResult;
52+
class QgsMapSettings;
53+
54+
/**
55+
* \class Handles asynchronous download of images
56+
*
57+
* \note added in 2.8
58+
*/
59+
class QgsImageFetcher : public QObject
60+
{
61+
Q_OBJECT
62+
public:
63+
64+
QgsImageFetcher() {};
65+
virtual ~QgsImageFetcher( ) {}
66+
67+
// Make sure to connect to "finish" and "error" before starting
68+
virtual void start() = 0;
69+
70+
signals:
71+
72+
void finish( const QImage& legend );
73+
void progress( qint64 received, qint64 total );
74+
void error( const QString& msg );
75+
};
76+
4977

5078
/** \ingroup core
5179
* Base class for raster data providers.
@@ -201,14 +229,39 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
201229
}
202230

203231
/** \brief Returns the legend rendered as pixmap
204-
* useful for that layer that need to get legend layer remotly as WMS */
205-
virtual QImage getLegendGraphic( double scale = 0, bool forceRefresh = false )
232+
*
233+
* useful for that layer that need to get legend layer remotely as WMS
234+
* \param visibleExtent Visible extent for providers supporting
235+
* contextual legends, in layer CRS
236+
* \note visibleExtent parameter added in 2.8
237+
*/
238+
virtual QImage getLegendGraphic( double scale = 0, bool forceRefresh = false, const QgsRectangle * visibleExtent = 0 )
206239
{
207240
Q_UNUSED( scale );
208241
Q_UNUSED( forceRefresh );
242+
Q_UNUSED( visibleExtent );
209243
return QImage();
210244
}
211245

246+
/**
247+
* \class Get an image downloader for the raster legend
248+
*
249+
* \param mapSettings map settings for legend providers supporting
250+
* contextual legends.
251+
*
252+
* \return a download handler or null if the provider does not support
253+
* legend at all. Ownership of the returned object is transferred
254+
* to caller.
255+
*
256+
* \note added in 2.8
257+
*
258+
*/
259+
virtual QgsImageFetcher* getLegendGraphicFetcher( const QgsMapSettings* mapSettings )
260+
{
261+
Q_UNUSED( mapSettings );
262+
return 0;
263+
}
264+
212265
/** \brief Create pyramid overviews */
213266
virtual QString buildPyramids( const QList<QgsRasterPyramid> & thePyramidList,
214267
const QString & theResamplingMethod = "NEAREST",

src/providers/wms/qgswmscapabilities.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ bool QgsWmsSettings::parseUri( QString uriString )
9393

9494
mCrsId = uri.param( "crs" );
9595

96+
mEnableContextualLegend = uri.param( "contextualWMSLegend" ).toInt();
97+
QgsDebugMsg( QString( "Contextual legend: %1" ).arg( mEnableContextualLegend ) );
98+
9699
mFeatureCount = uri.param( "featureCount" ).toInt(); // default to 0
97100

98101
return true;

src/providers/wms/qgswmscapabilities.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ class QgsWmsSettings
524524

525525
QString mCrsId;
526526

527+
bool mEnableContextualLegend;
528+
527529
friend class QgsWmsProvider;
528530
};
529531

0 commit comments

Comments
 (0)