QGIS API Documentation 3.99.0-Master (d270888f95f)
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 ***************************************************************************/
22
23#include "qgslayertree.h"
25#include "qgslegendrenderer.h"
26#include "qgsmapsettings.h"
28#include "qgsvectorlayer.h"
30#include "qgswmsrenderer.h"
31#include "qgswmsrequest.h"
33#include "qgswmsutils.h"
34
35#include <QImage>
36#include <QJsonDocument>
37#include <QJsonObject>
38#include <QString>
39
40using namespace Qt::StringLiterals;
41
42namespace QgsWms
43{
44 void writeGetLegendGraphics( QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response )
45 {
46 // get parameters from query
47 QgsWmsParameters parameters = request.wmsParameters();
48
49 // check parameters validity
50 // FIXME fail with png + mode
51 checkParameters( parameters );
52
53 // init render context
54 QgsWmsRenderContext context( project, serverIface );
57 context.setParameters( parameters );
58 context.setSocketFeedback( response.feedback() );
59
60 // get the requested output format
61 QgsWmsParameters::Format format = parameters.format();
62
63 // parameters.format() returns NONE if the requested format is image/png with a
64 // mode (e.g. image/png;mode=16bit), so in that case we use parseImageFormat to
65 // give the requested format another chance
66
67 QString imageSaveFormat;
68 QString imageContentType;
69 if ( format == QgsWmsParameters::Format::PNG )
70 {
71 imageContentType = "image/png";
72 imageSaveFormat = "PNG";
73 }
74 else if ( format == QgsWmsParameters::Format::JPG )
75 {
76 imageContentType = "image/jpeg";
77 imageSaveFormat = "JPEG";
78 }
79 else if ( format == QgsWmsParameters::Format::NONE )
80 {
81 switch ( parseImageFormat( parameters.formatAsString() ) )
82 {
88 imageContentType = "image/png";
89 imageSaveFormat = "PNG";
90 break;
92 break;
93
94 // not possible
97 break;
98 }
99 }
100
101 if ( format == QgsWmsParameters::Format::NONE )
102 {
103 throw QgsBadRequestException( QgsServiceException::OGC_InvalidFormat, u"Output format '%1' is not supported in the GetLegendGraphic request"_s.arg( parameters.formatAsString() ) );
104 }
105
106 // Get cached image
107#ifdef HAVE_SERVER_PYTHON_PLUGINS
108 QgsAccessControl *accessControl = serverIface->accessControls();
109 QgsServerCacheManager *cacheManager = serverIface->cacheManager();
110 if ( cacheManager && !imageSaveFormat.isEmpty() )
111 {
112 QImage image;
113 const QByteArray content = cacheManager->getCachedImage( project, request, accessControl );
114 if ( !content.isEmpty() && image.loadFromData( content ) )
115 {
116 response.setHeader( u"Content-Type"_s, imageContentType );
117 image.save( response.io(), qPrintable( imageSaveFormat ) );
118 return;
119 }
120 }
121#endif
122 QgsRenderer renderer( context );
123
124 // retrieve legend settings and model
125 bool addLegendGroups = QgsServerProjectUtils::wmsAddLegendGroupsLegendGraphic( *project ) || parameters.addLayerGroups();
126 std::unique_ptr<QgsLayerTree> tree( addLegendGroups ? layerTreeWithGroups( context, QgsProject::instance()->layerTreeRoot() ) : layerTree( context ) );
127 const std::unique_ptr<QgsLayerTreeModel> model( legendModel( context, *tree.get() ) );
128
129 // rendering
130 if ( format == QgsWmsParameters::Format::JSON )
131 {
132 QJsonObject result;
133
135
136 if ( parameters.showRuleDetailsAsBool() )
137 {
139 }
140
141 if ( !parameters.rule().isEmpty() )
142 {
143 QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
144 if ( !node )
145 {
146 throw QgsException( u"Could not get a legend node for the requested RULE"_s );
147 }
148 result = renderer.getLegendGraphicsAsJson( *node, jsonFlags );
149 }
150 else
151 {
152 result = renderer.getLegendGraphicsAsJson( *model.get(), jsonFlags );
153 }
154 tree->clear();
155 response.setHeader( u"Content-Type"_s, parameters.formatAsString() );
156 const QJsonDocument doc( result );
157 response.write( doc.toJson( QJsonDocument::Compact ) );
158 }
159 else
160 {
161 std::unique_ptr<QImage> result;
162 if ( !parameters.rule().isEmpty() )
163 {
164 QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
165 if ( !node )
166 {
167 throw QgsException( u"Could not get a legend node for the requested RULE"_s );
168 }
169 result.reset( renderer.getLegendGraphics( *node ) );
170 }
171 else
172 {
173 result.reset( renderer.getLegendGraphics( *model.get() ) );
174 }
175 tree->clear();
176 if ( result )
177 {
178 writeImage( response, *result, parameters.formatAsString(), context.imageQuality() );
179#ifdef HAVE_SERVER_PYTHON_PLUGINS
180 if ( cacheManager )
181 {
182 const QByteArray content = response.data();
183 if ( !content.isEmpty() )
184 cacheManager->setCachedImage( &content, project, request, accessControl );
185 }
186#endif
187 }
188 else
189 {
190 throw QgsException( u"Failed to compute GetLegendGraphics image"_s );
191 }
192 }
193 }
194
196 {
197 if ( parameters.allLayersNickname().isEmpty() )
198 {
200 }
201
202 if ( parameters.format() == QgsWmsParameters::Format::NONE )
203 {
205 }
206
207 if ( !parameters.bbox().isEmpty() && !parameters.rule().isEmpty() )
208 {
209 throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue, u"BBOX parameter cannot be combined with RULE."_s );
210 }
211
212 if ( !parameters.bbox().isEmpty() && parameters.bboxAsRectangle().isEmpty() )
213 {
215 }
216 // If we have a contextual legend (BBOX is set)
217 // make sure (SRC)WIDTH and (SRC)HEIGHT are set, default to 800px width
218 // height is calculated from that value, respecting the aspect
219 if ( !parameters.bbox().isEmpty() )
220 {
221 // Calculate ratio from bbox
222 QgsRectangle bbox { parameters.bboxAsRectangle() };
223 const QString crs = parameters.crs();
224 if ( crs.compare( u"CRS:84"_s, Qt::CaseInsensitive ) == 0 )
225 {
226 bbox.invert();
227 }
229 if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCrs.hasAxisInverted() )
230 {
231 bbox.invert();
232 }
233 const double ratio { bbox.width() / bbox.height() };
234 const int defaultHeight { static_cast<int>( 800 / ratio ) };
235 if ( parameters.width().isEmpty() && parameters.srcWidth().isEmpty() )
236 {
237 parameters.set( QgsWmsParameter::SRCWIDTH, 800 );
238 }
239 if ( parameters.height().isEmpty() && parameters.srcHeight().isEmpty() )
240 {
241 parameters.set( QgsWmsParameter::SRCHEIGHT, defaultHeight );
242 }
243 }
244 }
245
247 {
248 const QgsWmsParameters parameters = context.parameters();
249 auto model = std::make_unique<QgsLayerTreeModel>( &tree );
250 std::unique_ptr<QgsMapSettings> mapSettings;
251
252 if ( context.scaleDenominator() > 0 )
253 {
254 model->setLegendFilterByScale( context.scaleDenominator() );
255 }
256
257 // content based legend
258 if ( !parameters.bbox().isEmpty() )
259 {
260 mapSettings = std::make_unique<QgsMapSettings>();
261 mapSettings->setOutputSize( context.mapSize() );
262 // Inverted axis?
263 QgsRectangle bbox { parameters.bboxAsRectangle() };
264 const QString crs = parameters.crs();
265 if ( crs.compare( u"CRS:84"_s, Qt::CaseInsensitive ) == 0 )
266 {
267 bbox.invert();
268 }
270 if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCrs.hasAxisInverted() )
271 {
272 bbox.invert();
273 }
274 mapSettings->setDestinationCrs( outputCrs );
275 mapSettings->setExtent( bbox );
276 QgsRenderer renderer( context );
277 QList<QgsMapLayer *> layers = context.layersToRender();
278 renderer.configureLayers( layers, mapSettings.get() );
279 mapSettings->setLayers( context.layersToRender() );
280
281 QgsLayerTreeFilterSettings filterSettings( *mapSettings );
282 filterSettings.setLayerFilterExpressionsFromLayerTree( model->rootGroup() );
283 model->setFilterSettings( &filterSettings );
284 }
285
286 // if legend is not based on rendering rules
287 if ( parameters.rule().isEmpty() )
288 {
289 const QList<QgsLayerTreeNode *> children = tree.children();
290 const QString ruleLabel = parameters.ruleLabel();
291 for ( QgsLayerTreeNode *node : children )
292 {
293 if ( !QgsLayerTree::isLayer( node ) )
294 continue;
295
296 QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
297
298 // layer titles - hidden or not
300 // rule item titles
301 if ( !parameters.ruleLabelAsBool() )
302 {
303 for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
304 {
305 // empty string = no override, so let's use one space
306 legendNode->setUserLabel( u" "_s );
307 }
308 }
309 else if ( ruleLabel.compare( u"AUTO"_s, Qt::CaseInsensitive ) == 0 )
310 {
311 for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
312 {
313 //clearing label for single symbol
314 if ( legendNode->isEmbeddedInParent() )
315 legendNode->setEmbeddedInParent( false );
316 }
317 }
318 }
319 }
320
321 return model.release();
322 }
323
325 {
326 auto tree = std::make_unique<QgsLayerTree>();
327
328 QList<QgsVectorLayerFeatureCounter *> counters;
329 for ( QgsMapLayer *ml : context.layersToRender() )
330 {
331 QgsLayerTreeLayer *lt = tree->addLayer( ml );
332 lt->setUseLayerName( false ); // do not modify underlying layer
333
334 // name
335 if ( !ml->serverProperties()->title().isEmpty() )
336 lt->setName( ml->serverProperties()->title() );
337
338 // show feature count
339 const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
340 const QString property = u"showFeatureCount"_s;
341 lt->setCustomProperty( property, showFeatureCount );
342
343 if ( ml->type() != Qgis::LayerType::Vector || !showFeatureCount )
344 continue;
345
346 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
348 if ( !counter )
349 continue;
350
351 counters.append( counter );
352 }
353
354 for ( QgsVectorLayerFeatureCounter *counter : counters )
355 {
356 counter->waitForFinished();
357 }
358
359 return tree.release();
360 }
361
363 {
364 if ( !projectRoot )
365 {
366 return nullptr;
367 }
368
369 auto tree = std::make_unique<QgsLayerTree>();
370
371 QgsWmsParameters wmsParams = context.parameters();
372 QStringList layerNicknames = wmsParams.allLayersNickname();
373 for ( int i = 0; i < layerNicknames.size(); ++i )
374 {
375 QString nickname = layerNicknames.at( i );
376
377 //single layer
378 QgsMapLayer *layer = context.layer( nickname );
379 if ( layer )
380 {
381 tree->addLayer( layer );
382 }
383 else //nickname refers to a group
384 {
385 QgsLayerTreeGroup *group = projectRoot->findGroup( nickname );
386 if ( group )
387 {
388 tree->insertChildNode( i, group->clone() );
389 }
390 }
391 }
392
393 return tree.release();
394 }
395
397 {
398 for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
399 {
400 for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
401 {
402 if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
403 return node;
404 }
405 }
406 return nullptr;
407 }
408} // namespace QgsWms
@ Hidden
Special style, item is hidden including margins around.
Definition qgis.h:4636
@ Subgroup
Legend subgroup title.
Definition qgis.h:4639
@ ShowRuleDetails
If set, the rule expression of a rule based renderer legend item will be added to the JSON.
Definition qgis.h:4668
@ Vector
Vector layer.
Definition qgis.h:194
QFlags< LegendJsonRenderFlag > LegendJsonRenderFlags
Definition qgis.h:4671
A helper class that centralizes restrictions given by all the access control filter plugins.
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.
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.
An abstract interface for legend items returned from QgsMapLayerLegend implementation.
A model representing the layer tree, including layers and groups of layers.
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.
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, Qgis::LegendComponent style)
Sets the style of a node.
Base class for all map layer types.
Definition qgsmaplayer.h:83
Describes the version of a project.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:112
static QgsProject * instance()
Returns the QgsProject singleton instance.
A rectangle specified with double values.
void invert()
Swap x/y coordinates in the rectangle.
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.
Defines 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.
static bool wmsAddLegendGroupsLegendGraphic(const QgsProject &project)
Returns if legend groups should be in the legend graphic response if GetLegendGraphic is called on a ...
Defines the response interface passed to QgsService.
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.
Counts the features in a QgsVectorLayer in task.
Represents a vector layer which manages a vector based dataset.
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.
Defines request interfaces passed to WMS service.
const QgsWmsParameters & wmsParameters() const
Returns the parameters interpreted for the WMS service.
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.
Definition qgswmsutils.h:42
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...