QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
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"
26
27#include "qgswmsutils.h"
28#include "qgswmsrequest.h"
31#include "qgswmsrenderer.h"
33#include "qgsmapsettings.h"
34
35#include <QImage>
36#include <QJsonObject>
37#include <QJsonDocument>
38
39namespace QgsWms
40{
41 void writeGetLegendGraphics( QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response )
42 {
43 // get parameters from query
44 QgsWmsParameters parameters = request.wmsParameters();
45
46 // check parameters validity
47 // FIXME fail with png + mode
48 checkParameters( parameters );
49
50 // init render context
51 QgsWmsRenderContext context( project, serverIface );
54 context.setParameters( parameters );
55 context.setSocketFeedback( response.feedback() );
56
57 // get the requested output format
58 QgsWmsParameters::Format format = parameters.format();
59
60 // parameters.format() returns NONE if the requested format is image/png with a
61 // mode (e.g. image/png;mode=16bit), so in that case we use parseImageFormat to
62 // give the requested format another chance
63
64 QString imageSaveFormat;
65 QString imageContentType;
66 if ( format == QgsWmsParameters::Format::PNG )
67 {
68 imageContentType = "image/png";
69 imageSaveFormat = "PNG";
70 }
71 else if ( format == QgsWmsParameters::Format::JPG )
72 {
73 imageContentType = "image/jpeg";
74 imageSaveFormat = "JPEG";
75 }
76 else if ( format == QgsWmsParameters::Format::NONE )
77 {
78 switch ( parseImageFormat( parameters.formatAsString() ) )
79 {
85 imageContentType = "image/png";
86 imageSaveFormat = "PNG";
87 break;
89 break;
90
91 // not possible
94 break;
95 }
96 }
97
98 if ( format == QgsWmsParameters::Format::NONE )
99 {
100 throw QgsBadRequestException( QgsServiceException::OGC_InvalidFormat, QStringLiteral( "Output format '%1' is not supported in the GetLegendGraphic request" ).arg( parameters.formatAsString() ) );
101 }
102
103 // Get cached image
104#ifdef HAVE_SERVER_PYTHON_PLUGINS
105 QgsAccessControl *accessControl = serverIface->accessControls();
106 QgsServerCacheManager *cacheManager = serverIface->cacheManager();
107 if ( cacheManager && !imageSaveFormat.isEmpty() )
108 {
109 QImage image;
110 const QByteArray content = cacheManager->getCachedImage( project, request, accessControl );
111 if ( !content.isEmpty() && image.loadFromData( content ) )
112 {
113 response.setHeader( QStringLiteral( "Content-Type" ), imageContentType );
114 image.save( response.io(), qPrintable( imageSaveFormat ) );
115 return;
116 }
117 }
118#endif
119 QgsRenderer renderer( context );
120
121 // retrieve legend settings and model
122 bool addLegendGroups = QgsServerProjectUtils::wmsAddLegendGroupsLegendGraphic( *project ) || parameters.addLayerGroups();
123 std::unique_ptr<QgsLayerTree> tree( addLegendGroups ? layerTreeWithGroups( context, QgsProject::instance()->layerTreeRoot() ) : layerTree( context ) );
124 const std::unique_ptr<QgsLayerTreeModel> model( legendModel( context, *tree.get() ) );
125
126 // rendering
127 if ( format == QgsWmsParameters::Format::JSON )
128 {
129 QJsonObject result;
130
132
133 if ( parameters.showRuleDetailsAsBool() )
134 {
136 }
137
138 if ( !parameters.rule().isEmpty() )
139 {
140 QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
141 if ( !node )
142 {
143 throw QgsException( QStringLiteral( "Could not get a legend node for the requested RULE" ) );
144 }
145 result = renderer.getLegendGraphicsAsJson( *node, jsonFlags );
146 }
147 else
148 {
149 result = renderer.getLegendGraphicsAsJson( *model.get(), jsonFlags );
150 }
151 tree->clear();
152 response.setHeader( QStringLiteral( "Content-Type" ), parameters.formatAsString() );
153 const QJsonDocument doc( result );
154 response.write( doc.toJson( QJsonDocument::Compact ) );
155 }
156 else
157 {
158 std::unique_ptr<QImage> result;
159 if ( !parameters.rule().isEmpty() )
160 {
161 QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
162 if ( !node )
163 {
164 throw QgsException( QStringLiteral( "Could not get a legend node for the requested RULE" ) );
165 }
166 result.reset( renderer.getLegendGraphics( *node ) );
167 }
168 else
169 {
170 result.reset( renderer.getLegendGraphics( *model.get() ) );
171 }
172 tree->clear();
173 if ( result )
174 {
175 writeImage( response, *result, parameters.formatAsString(), context.imageQuality() );
176#ifdef HAVE_SERVER_PYTHON_PLUGINS
177 if ( cacheManager )
178 {
179 const QByteArray content = response.data();
180 if ( !content.isEmpty() )
181 cacheManager->setCachedImage( &content, project, request, accessControl );
182 }
183#endif
184 }
185 else
186 {
187 throw QgsException( QStringLiteral( "Failed to compute GetLegendGraphics image" ) );
188 }
189 }
190 }
191
193 {
194 if ( parameters.allLayersNickname().isEmpty() )
195 {
197 }
198
199 if ( parameters.format() == QgsWmsParameters::Format::NONE )
200 {
202 }
203
204 if ( !parameters.bbox().isEmpty() && !parameters.rule().isEmpty() )
205 {
206 throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue, QStringLiteral( "BBOX parameter cannot be combined with RULE." ) );
207 }
208
209 if ( !parameters.bbox().isEmpty() && parameters.bboxAsRectangle().isEmpty() )
210 {
212 }
213 // If we have a contextual legend (BBOX is set)
214 // make sure (SRC)WIDTH and (SRC)HEIGHT are set, default to 800px width
215 // height is calculated from that value, respecting the aspect
216 if ( !parameters.bbox().isEmpty() )
217 {
218 // Calculate ratio from bbox
219 QgsRectangle bbox { parameters.bboxAsRectangle() };
220 const QString crs = parameters.crs();
221 if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
222 {
223 bbox.invert();
224 }
226 if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCrs.hasAxisInverted() )
227 {
228 bbox.invert();
229 }
230 const double ratio { bbox.width() / bbox.height() };
231 const int defaultHeight { static_cast<int>( 800 / ratio ) };
232 if ( parameters.width().isEmpty() && parameters.srcWidth().isEmpty() )
233 {
234 parameters.set( QgsWmsParameter::SRCWIDTH, 800 );
235 }
236 if ( parameters.height().isEmpty() && parameters.srcHeight().isEmpty() )
237 {
238 parameters.set( QgsWmsParameter::SRCHEIGHT, defaultHeight );
239 }
240 }
241 }
242
244 {
245 const QgsWmsParameters parameters = context.parameters();
246 std::unique_ptr<QgsLayerTreeModel> model( new QgsLayerTreeModel( &tree ) );
247 std::unique_ptr<QgsMapSettings> mapSettings;
248
249 if ( context.scaleDenominator() > 0 )
250 {
251 model->setLegendFilterByScale( context.scaleDenominator() );
252 }
253
254 // content based legend
255 if ( !parameters.bbox().isEmpty() )
256 {
257 mapSettings = std::make_unique<QgsMapSettings>();
258 mapSettings->setOutputSize( context.mapSize() );
259 // Inverted axis?
260 QgsRectangle bbox { parameters.bboxAsRectangle() };
261 const QString crs = parameters.crs();
262 if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
263 {
264 bbox.invert();
265 }
267 if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCrs.hasAxisInverted() )
268 {
269 bbox.invert();
270 }
271 mapSettings->setDestinationCrs( outputCrs );
272 mapSettings->setExtent( bbox );
273 QgsRenderer renderer( context );
274 QList<QgsMapLayer *> layers = context.layersToRender();
275 renderer.configureLayers( layers, mapSettings.get() );
276 mapSettings->setLayers( context.layersToRender() );
277
278 QgsLayerTreeFilterSettings filterSettings( *mapSettings );
279 filterSettings.setLayerFilterExpressionsFromLayerTree( model->rootGroup() );
280 model->setFilterSettings( &filterSettings );
281 }
282
283 // if legend is not based on rendering rules
284 if ( parameters.rule().isEmpty() )
285 {
286 const QList<QgsLayerTreeNode *> children = tree.children();
287 const QString ruleLabel = parameters.ruleLabel();
288 for ( QgsLayerTreeNode *node : children )
289 {
290 if ( !QgsLayerTree::isLayer( node ) )
291 continue;
292
293 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
294
295 // layer titles - hidden or not
297 // rule item titles
298 if ( !parameters.ruleLabelAsBool() )
299 {
300 for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
301 {
302 // empty string = no override, so let's use one space
303 legendNode->setUserLabel( QStringLiteral( " " ) );
304 }
305 }
306 else if ( ruleLabel.compare( QStringLiteral( "AUTO" ), Qt::CaseInsensitive ) == 0 )
307 {
308 for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
309 {
310 //clearing label for single symbol
313 }
314 }
315 }
316 }
317
318 return model.release();
319 }
320
322 {
323 std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
324
325 QList<QgsVectorLayerFeatureCounter *> counters;
326 for ( QgsMapLayer *ml : context.layersToRender() )
327 {
328 QgsLayerTreeLayer *lt = tree->addLayer( ml );
329 lt->setUseLayerName( false ); // do not modify underlying layer
330
331 // name
332 if ( !ml->serverProperties()->title().isEmpty() )
333 lt->setName( ml->serverProperties()->title() );
334
335 // show feature count
336 const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
337 const QString property = QStringLiteral( "showFeatureCount" );
338 lt->setCustomProperty( property, showFeatureCount );
339
340 if ( ml->type() != Qgis::LayerType::Vector || !showFeatureCount )
341 continue;
342
343 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
345 if ( !counter )
346 continue;
347
348 counters.append( counter );
349 }
350
351 for ( QgsVectorLayerFeatureCounter *counter : counters )
352 {
353 counter->waitForFinished();
354 }
355
356 return tree.release();
357 }
358
360 {
361 if ( !projectRoot )
362 {
363 return 0;
364 }
365
366 std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
367
368 QgsWmsParameters wmsParams = context.parameters();
369 QStringList layerNicknames = wmsParams.allLayersNickname();
370 for ( int i = 0; i < layerNicknames.size(); ++i )
371 {
372 QString nickname = layerNicknames.at( i );
373
374 //single layer
375 QgsMapLayer *layer = context.layer( nickname );
376 if ( layer )
377 {
378 tree->addLayer( layer );
379 }
380 else //nickname refers to a group
381 {
382 QgsLayerTreeGroup *group = projectRoot->findGroup( nickname );
383 if ( group )
384 {
385 tree->insertChildNode( i, group->clone() );
386 }
387 }
388 }
389
390 return tree.release();
391 }
392
394 {
395 for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
396 {
397 for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
398 {
399 if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
400 return node;
401 }
402 }
403 return nullptr;
404 }
405} // namespace QgsWms
@ ShowRuleDetails
If set, the rule expression of a rule based renderer legend item will be added to the JSON.
@ Vector
Vector layer.
QFlags< LegendJsonRenderFlag > LegendJsonRenderFlags
Definition qgis.h:4327
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 the axis order is inverted for the CRS compared to the order east/north (longitude/la...
Defines a QGIS exception class.
Contains settings relating to filtering the contents of QgsLayerTreeModel and views.
void setLayerFilterExpressionsFromLayerTree(QgsLayerTree *tree)
Sets layer filter expressions using a layer tree.
Layer tree group node serves as a container for layers and further groups.
void insertChildNode(int index, QgsLayerTreeNode *node)
Insert existing node at specified position.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
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.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
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:76
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:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
A rectangle specified with double values.
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 QgsFeedback * feedback() const
Returns the socket feedback if any.
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.
QImage * getLegendGraphics(QgsLayerTreeModel &model)
Returns the map legend as an image (or nullptr in case of error).
QJsonObject getLegendGraphicsAsJson(QgsLayerTreeModel &model, const Qgis::LegendJsonRenderFlags &jsonRenderFlags=Qgis::LegendJsonRenderFlags())
Returns the map legend as a JSON object.
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 showRuleDetailsAsBool() const
Returns SHOWRULEDETAILS as a bool.
bool layerTitleAsBool() const
Returns LAYERTITLE as a bool or its default value if not defined.
bool addLayerGroups() const
Returns true if layer groups shall be added to GetLegendGraphic results.
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.
QgsMapLayer * layer(const QString &nickname) const
Returns the layer corresponding to the nickname, or a nullptr if not found or if the layer do not nee...
void setFlag(Flag flag, bool on=true)
Sets or unsets a rendering flag according to the on value.
QgsWmsParameters parameters() const
Returns WMS parameters.
void setSocketFeedback(QgsFeedback *feedback)
Sets the response feedback.
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.
const QgsWmsParameters & wmsParameters() const
Returns the parameters interpreted for the WMS service.
SERVER_EXPORT bool wmsAddLegendGroupsLegendGraphic(const QgsProject &project)
Returns if legend groups should be in the legend graphic response if GetLegendGraphic is called on a ...
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)
QgsLayerTree * layerTreeWithGroups(const QgsWmsRenderContext &context, QgsLayerTree *projectRoot)
@ Unknown
Unknown/invalid format.
ImageOutputFormat parseImageFormat(const QString &format)
Parse image format parameter.
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