QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgswmsgetlegendgraphics.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgswmsgetlegendgraphics.cpp
3 -------------------------
4 begin : December 20 , 2016
5 copyright : (C) 2007 by Marco Hugentobler (original code)
6 (C) 2014 by Alessandro Pasotti (original code)
7 (C) 2016 by David Marteau
8 email : marco dot hugentobler at karto dot baug dot ethz dot ch
9 a dot pasotti at itopen dot it
10 david dot marteau at 3liz dot com
11 ***************************************************************************/
12
13/***************************************************************************
14 * *
15 * This program is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU General Public License as published by *
17 * the Free Software Foundation; either version 2 of the License, or *
18 * (at your option) any later version. *
19 * *
20 ***************************************************************************/
21#include "qgslayertree.h"
22#include "qgslegendrenderer.h"
23#include "qgsvectorlayer.h"
25
26#include "qgswmsutils.h"
27#include "qgswmsrequest.h"
30#include "qgswmsrenderer.h"
31
32#include <QImage>
33#include <QJsonObject>
34#include <QJsonDocument>
35
36namespace QgsWms
37{
38 void writeGetLegendGraphics( QgsServerInterface *serverIface, const QgsProject *project,
39 const QgsWmsRequest &request,
40 QgsServerResponse &response )
41 {
42 // get parameters from query
43 QgsWmsParameters parameters = request.wmsParameters();
44
45 // check parameters validity
46 // FIXME fail with png + mode
47 checkParameters( parameters );
48
49 // init render context
50 QgsWmsRenderContext context( project, serverIface );
53 context.setParameters( parameters );
54
55 // get the requested output format
56 QgsWmsParameters::Format format = parameters.format();
57
58 // parameters.format() returns NONE if the requested format is image/png with a
59 // mode (e.g. image/png;mode=16bit), so in that case we use parseImageFormat to
60 // give the requested format another chance
61
62 QString imageSaveFormat;
63 QString imageContentType;
64 if ( format == QgsWmsParameters::Format::PNG )
65 {
66 imageContentType = "image/png";
67 imageSaveFormat = "PNG";
68 }
69 else if ( format == QgsWmsParameters::Format::JPG )
70 {
71 imageContentType = "image/jpeg";
72 imageSaveFormat = "JPEG";
73 }
74 else if ( format == QgsWmsParameters::Format::NONE )
75 {
76 switch ( parseImageFormat( parameters.formatAsString() ) )
77 {
82 format = QgsWmsParameters::Format::PNG;
83 imageContentType = "image/png";
84 imageSaveFormat = "PNG";
85 break;
87 break;
88
89 // not possible
92 break;
93 }
94 }
95
96 if ( format == QgsWmsParameters::Format::NONE )
97 {
99 QStringLiteral( "Output format '%1' is not supported in the GetLegendGraphic request" ).arg( parameters.formatAsString() ) );
100 }
101
102 // Get cached image
103#ifdef HAVE_SERVER_PYTHON_PLUGINS
104 QgsAccessControl *accessControl = serverIface->accessControls();
105 QgsServerCacheManager *cacheManager = serverIface->cacheManager();
106 if ( cacheManager && !imageSaveFormat.isEmpty() )
107 {
108 QImage image;
109 const QByteArray content = cacheManager->getCachedImage( project, request, accessControl );
110 if ( !content.isEmpty() && image.loadFromData( content ) )
111 {
112 response.setHeader( QStringLiteral( "Content-Type" ), imageContentType );
113 image.save( response.io(), qPrintable( imageSaveFormat ) );
114 return;
115 }
116 }
117#endif
118 QgsRenderer renderer( context );
119
120 // retrieve legend settings and model
121 std::unique_ptr<QgsLayerTree> tree( layerTree( context ) );
122 const std::unique_ptr<QgsLayerTreeModel> model( legendModel( context, *tree.get() ) );
123
124 // rendering
125 if ( format == QgsWmsParameters::Format::JSON )
126 {
127 QJsonObject result;
128 if ( !parameters.rule().isEmpty() )
129 {
131 QStringLiteral( "RULE cannot be used with JSON format" ) );
132 }
133 else
134 {
135 result = renderer.getLegendGraphicsAsJson( *model.get() );
136 }
137 tree->clear();
138 response.setHeader( QStringLiteral( "Content-Type" ), parameters.formatAsString() );
139 const QJsonDocument doc( result );
140 response.write( doc.toJson( QJsonDocument::Compact ) );
141 }
142 else
143 {
144 std::unique_ptr<QImage> result;
145 if ( !parameters.rule().isEmpty() )
146 {
147 QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
148 if ( ! node )
149 {
150 throw QgsException( QStringLiteral( "Could not get a legend node for the requested RULE" ) );
151 }
152 result.reset( renderer.getLegendGraphics( *node ) );
153 }
154 else
155 {
156 result.reset( renderer.getLegendGraphics( *model.get() ) );
157 }
158 tree->clear();
159 if ( result )
160 {
161 writeImage( response, *result, parameters.formatAsString(), context.imageQuality() );
162#ifdef HAVE_SERVER_PYTHON_PLUGINS
163 if ( cacheManager )
164 {
165 const QByteArray content = response.data();
166 if ( !content.isEmpty() )
167 cacheManager->setCachedImage( &content, project, request, accessControl );
168 }
169#endif
170 }
171 else
172 {
173 throw QgsException( QStringLiteral( "Failed to compute GetLegendGraphics image" ) );
174 }
175 }
176 }
177
179 {
180 if ( parameters.allLayersNickname().isEmpty() )
181 {
183 parameters[QgsWmsParameter::LAYERS] );
184 }
185
186 if ( parameters.format() == QgsWmsParameters::Format::NONE )
187 {
189 parameters[QgsWmsParameter::FORMAT] );
190 }
191
192 if ( ! parameters.bbox().isEmpty() && !parameters.rule().isEmpty() )
193 {
195 QStringLiteral( "BBOX parameter cannot be combined with RULE." ) );
196 }
197
198 if ( ! parameters.bbox().isEmpty() && parameters.bboxAsRectangle().isEmpty() )
199 {
201 parameters[QgsWmsParameter::BBOX] );
202 }
203 // If we have a contextual legend (BBOX is set)
204 // make sure (SRC)WIDTH and (SRC)HEIGHT are set, default to 800px width
205 // height is calculated from that value, respecting the aspect
206 if ( ! parameters.bbox().isEmpty() )
207 {
208 // Calculate ratio from bbox
209 QgsRectangle bbox { parameters.bboxAsRectangle() };
210 const QString crs = parameters.crs();
211 if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
212 {
213 bbox.invert();
214 }
216 if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
218 {
219 bbox.invert();
220 }
221 const double ratio { bbox.width() / bbox.height() };
222 const int defaultHeight { static_cast<int>( 800 / ratio ) };
223 if ( parameters.width().isEmpty() && parameters.srcWidth().isEmpty() )
224 {
225 parameters.set( QgsWmsParameter::SRCWIDTH, 800 );
226 }
227 if ( parameters.height().isEmpty() && parameters.srcHeight().isEmpty() )
228 {
229 parameters.set( QgsWmsParameter::SRCHEIGHT, defaultHeight );
230 }
231 }
232 }
233
235 {
236
237 const QgsWmsParameters parameters = context.parameters();
238 std::unique_ptr<QgsLayerTreeModel> model( new QgsLayerTreeModel( &tree ) );
239 std::unique_ptr<QgsMapSettings> mapSettings;
240
241 if ( context.scaleDenominator() > 0 )
242 {
243 model->setLegendFilterByScale( context.scaleDenominator() );
244 }
245
246 // content based legend
247 if ( ! parameters.bbox().isEmpty() )
248 {
249 mapSettings = std::make_unique<QgsMapSettings>();
250 mapSettings->setOutputSize( context.mapSize() );
251 // Inverted axis?
252 QgsRectangle bbox { parameters.bboxAsRectangle() };
253 const QString crs = parameters.crs();
254 if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
255 {
256 bbox.invert();
257 }
259 if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
261 {
262 bbox.invert();
263 }
264 mapSettings->setDestinationCrs( outputCrs );
265 mapSettings->setExtent( bbox );
266 QgsRenderer renderer( context );
267 QList<QgsMapLayer *> layers = context.layersToRender();
268 renderer.configureLayers( layers, mapSettings.get() );
269 mapSettings->setLayers( context.layersToRender() );
270 model->setLegendFilterByMap( mapSettings.get() );
271 }
272
273 // if legend is not based on rendering rules
274 if ( parameters.rule().isEmpty() )
275 {
276 const QList<QgsLayerTreeNode *> children = tree.children();
277 const QString ruleLabel = parameters.ruleLabel();
278 for ( QgsLayerTreeNode *node : children )
279 {
280 if ( ! QgsLayerTree::isLayer( node ) )
281 continue;
282
283 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
284
285 // layer titles - hidden or not
287 // rule item titles
288 if ( !parameters.ruleLabelAsBool() )
289 {
290 for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
291 {
292 // empty string = no override, so let's use one space
293 legendNode->setUserLabel( QStringLiteral( " " ) );
294 }
295 }
296 else if ( ruleLabel.compare( QStringLiteral( "AUTO" ), Qt::CaseInsensitive ) == 0 )
297 {
298 for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
299 {
300 //clearing label for single symbol
303 }
304 }
305 }
306 }
307
308 return model.release();
309 }
310
312 {
313 std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
314
315 QList<QgsVectorLayerFeatureCounter *> counters;
316 for ( QgsMapLayer *ml : context.layersToRender() )
317 {
318 QgsLayerTreeLayer *lt = tree->addLayer( ml );
319 lt->setUseLayerName( false ); // do not modify underlying layer
320
321 // name
322 if ( !ml->title().isEmpty() )
323 lt->setName( ml->title() );
324
325 // show feature count
326 const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
327 const QString property = QStringLiteral( "showFeatureCount" );
328 lt->setCustomProperty( property, showFeatureCount );
329
330 if ( ml->type() != QgsMapLayerType::VectorLayer || !showFeatureCount )
331 continue;
332
333 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
335 if ( !counter )
336 continue;
337
338 counters.append( counter );
339 }
340
341 for ( QgsVectorLayerFeatureCounter *counter : counters )
342 {
343 counter->waitForFinished();
344 }
345
346 return tree.release();
347 }
348
350 {
351 for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
352 {
353 for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
354 {
355 if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
356 return node;
357 }
358 }
359 return nullptr;
360 }
361} // namespace QgsWms
A helper class that centralizes restrictions given by all the access control filter plugins.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
Defines a QGIS exception class.
Definition: qgsexception.h:35
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Layer tree node points to a map layer.
void setName(const QString &n) override
Sets the layer's name.
void setUseLayerName(bool use=true)
Uses the layer's name if use is true, or the name manually set if false.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
virtual void setEmbeddedInParent(bool embedded)
virtual void setUserLabel(const QString &userLabel)
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QList< QgsLayerTreeModelLegendNode * > layerLegendNodes(QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent=false)
Returns filtered list of active legend nodes attached to a particular layer node (by default it retur...
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
This class is a base class for nodes in a layer tree.
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:33
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
@ Subgroup
Legend subgroup title.
@ Hidden
Special style, item is hidden including margins around.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
A class to describe the version of a project.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
A helper class that centralizes caches accesses given by all the server cache filter plugins.
bool setCachedImage(const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Updates or inserts the image in cache like tiles.
QByteArray getCachedImage(const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Returns cached image (or 0 if image not in cache) like tiles.
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins.
virtual QgsServerCacheManager * cacheManager() const =0
Gets the registered server cache filters.
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual QByteArray data() const =0
Gets the data written so far.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
virtual QIODevice * io()=0
Returns the underlying QIODevice.
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
Counts the features in a QgsVectorLayer in task.
Represents a vector layer which manages a vector based data sets.
QgsVectorLayerFeatureCounter * countSymbolFeatures(bool storeSymbolFids=false)
Count features for symbols.
Exception thrown in case of malformed request.
Map renderer for WMS requests.
void configureLayers(QList< QgsMapLayer * > &layers, QgsMapSettings *settings=nullptr)
Configures layers for rendering optionally considering the map settings.
QJsonObject getLegendGraphicsAsJson(QgsLayerTreeModel &model)
Returns the map legend as a JSON object.
QImage * getLegendGraphics(QgsLayerTreeModel &model)
Returns the map legend as an image (or nullptr in case of error).
Provides an interface to retrieve and manipulate WMS parameters received from the client.
QString rule() const
Returns RULE parameter or an empty string if none is defined.
QStringList allLayersNickname() const
Returns nickname of layers found in LAYER and LAYERS parameters.
QString formatAsString() const
Returns FORMAT parameter as a string.
QgsProjectVersion versionAsNumber() const
Returns VERSION parameter if defined or its default value.
QString ruleLabel() const
Returns RULELABEL parameter or an empty string if none is defined.
QgsRectangle bboxAsRectangle() const
Returns BBOX as a rectangle if defined and valid.
void set(QgsWmsParameter::Name name, const QVariant &value)
Sets a parameter value thanks to its name.
QString srcHeight() const
Returns SRCHEIGHT parameter or an empty string if not defined.
bool showFeatureCountAsBool() const
Returns SHOWFEATURECOUNT as a bool.
QString bbox() const
Returns BBOX if defined or an empty string.
Format format() const
Returns format.
bool ruleLabelAsBool() const
Returns RULELABEL as a bool.
QString srcWidth() const
Returns SRCWIDTH parameter or an empty string if not defined.
QString height() const
Returns HEIGHT parameter or an empty string if not defined.
QString crs() const
Returns CRS or an empty string if none is defined.
bool layerTitleAsBool() const
Returns LAYERTITLE as a bool or its default value if not defined.
Format
Output format for the response.
QString width() const
Returns WIDTH parameter or an empty string if not defined.
Rendering context for the WMS renderer.
QSize mapSize(bool aspectRatio=true) const
Returns the size (in pixels) of the map to render, according to width and height WMS parameters as we...
void setParameters(const QgsWmsParameters &parameters)
Sets WMS parameters.
QList< QgsMapLayer * > layersToRender() const
Returns a list of all layers to actually render according to the current configuration.
void setFlag(Flag flag, bool on=true)
Sets or unsets a rendering flag according to the on value.
QgsWmsParameters parameters() const
Returns WMS parameters.
double scaleDenominator() const
Returns the scale denominator to use for rendering according to the current configuration.
int imageQuality() const
Returns the image quality to use for rendering according to the current configuration.
Class defining request interface passed to WMS service.
Definition: qgswmsrequest.h:35
const QgsWmsParameters & wmsParameters() const
Returns the parameters interpreted for the WMS service.
@ VectorLayer
Vector layer.
Median cut implementation.
void writeImage(QgsServerResponse &response, QImage &img, const QString &formatStr, int imageQuality)
Write image response.
void writeGetLegendGraphics(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response)
Output GetLegendGRaphics response.
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
QgsLayerTreeModel * legendModel(const QgsWmsRenderContext &context, QgsLayerTree &tree)
@ Unknown
Unknown/invalid format.
ImageOutputFormat parseImageFormat(const QString &format)
Parse image format parameter.
Definition: qgswmsutils.cpp:74
void checkParameters(QgsWmsParameters &parameters)
checkParameters checks request parameters and sets SRCHEIGHT and SRCWIDTH to default values in case B...
const QgsCoordinateReferenceSystem & outputCrs
const QgsCoordinateReferenceSystem & crs