QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 #include "qgssymbollayerutils.h"
26 #include "qgsmaplayerlegend.h"
27 
28 #include "qgswmsutils.h"
29 #include "qgswmsrequest.h"
30 #include "qgswmsserviceexception.h"
32 #include "qgswmsrenderer.h"
33 
34 #include <QImage>
35 #include <QJsonObject>
36 #include <QJsonDocument>
37 
38 namespace QgsWms
39 {
40  void writeGetLegendGraphics( QgsServerInterface *serverIface, const QgsProject *project,
41  const QgsWmsRequest &request,
42  QgsServerResponse &response )
43  {
44  // get parameters from query
45  QgsWmsParameters parameters = request.wmsParameters();
46 
47  // check parameters validity
48  // FIXME fail with png + mode
49  checkParameters( parameters );
50 
51  // init render context
52  QgsWmsRenderContext context( project, serverIface );
54  context.setFlag( QgsWmsRenderContext::UseSrcWidthHeight );
55  context.setParameters( parameters );
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  {
80  case PNG:
81  case PNG8:
82  case PNG16:
83  case PNG1:
85  imageContentType = "image/png";
86  imageSaveFormat = "PNG";
87  break;
88  default:
89  break;
90  }
91  }
92 
93  if ( format == QgsWmsParameters::Format::NONE )
94  {
96  QStringLiteral( "Output format '%1' is not supported in the GetLegendGraphic request" ).arg( parameters.formatAsString() ) );
97  }
98 
99  // Get cached image
100 #ifdef HAVE_SERVER_PYTHON_PLUGINS
101  QgsAccessControl *accessControl = serverIface->accessControls();
102  QgsServerCacheManager *cacheManager = serverIface->cacheManager();
103  if ( cacheManager && !imageSaveFormat.isEmpty() )
104  {
105  QImage image;
106  const QByteArray content = cacheManager->getCachedImage( project, request, accessControl );
107  if ( !content.isEmpty() && image.loadFromData( content ) )
108  {
109  response.setHeader( QStringLiteral( "Content-Type" ), imageContentType );
110  image.save( response.io(), qPrintable( imageSaveFormat ) );
111  return;
112  }
113  }
114 #endif
115  QgsRenderer renderer( context );
116 
117  // retrieve legend settings and model
118  std::unique_ptr<QgsLayerTree> tree( layerTree( context ) );
119  const std::unique_ptr<QgsLayerTreeModel> model( legendModel( context, *tree.get() ) );
120 
121  // rendering
122  if ( format == QgsWmsParameters::Format::JSON )
123  {
124  QJsonObject result;
125  if ( !parameters.rule().isEmpty() )
126  {
128  QStringLiteral( "RULE cannot be used with JSON format" ) );
129  }
130  else
131  {
132  result = renderer.getLegendGraphicsAsJson( *model.get() );
133  }
134  tree->clear();
135  response.setHeader( QStringLiteral( "Content-Type" ), parameters.formatAsString() );
136  const QJsonDocument doc( result );
137  response.write( doc.toJson( QJsonDocument::Compact ) );
138  }
139  else
140  {
141  std::unique_ptr<QImage> result;
142  if ( !parameters.rule().isEmpty() )
143  {
144  QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
145  if ( ! node )
146  {
147  throw QgsException( QStringLiteral( "Could not get a legend node for the requested RULE" ) );
148  }
149  result.reset( renderer.getLegendGraphics( *node ) );
150  }
151  else
152  {
153  result.reset( renderer.getLegendGraphics( *model.get() ) );
154  }
155  tree->clear();
156  if ( result )
157  {
158  writeImage( response, *result, parameters.formatAsString(), context.imageQuality() );
159 #ifdef HAVE_SERVER_PYTHON_PLUGINS
160  if ( cacheManager )
161  {
162  const QByteArray content = response.data();
163  if ( !content.isEmpty() )
164  cacheManager->setCachedImage( &content, project, request, accessControl );
165  }
166 #endif
167  }
168  else
169  {
170  throw QgsException( QStringLiteral( "Failed to compute GetLegendGraphics image" ) );
171  }
172  }
173  }
174 
175  void checkParameters( QgsWmsParameters &parameters )
176  {
177  if ( parameters.allLayersNickname().isEmpty() )
178  {
180  parameters[QgsWmsParameter::LAYERS] );
181  }
182 
183  if ( parameters.format() == QgsWmsParameters::Format::NONE )
184  {
186  parameters[QgsWmsParameter::FORMAT] );
187  }
188 
189  if ( ! parameters.bbox().isEmpty() && !parameters.rule().isEmpty() )
190  {
192  QStringLiteral( "BBOX parameter cannot be combined with RULE." ) );
193  }
194 
195  if ( ! parameters.bbox().isEmpty() && parameters.bboxAsRectangle().isEmpty() )
196  {
198  parameters[QgsWmsParameter::BBOX] );
199  }
200  // If we have a contextual legend (BBOX is set)
201  // make sure (SRC)WIDTH and (SRC)HEIGHT are set, default to 800px width
202  // height is calculated from that value, respecting the aspect
203  if ( ! parameters.bbox().isEmpty() )
204  {
205  // Calculate ratio from bbox
206  QgsRectangle bbox { parameters.bboxAsRectangle() };
207  const QString crs = parameters.crs();
208  if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
209  {
210  bbox.invert();
211  }
213  if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
215  {
216  bbox.invert();
217  }
218  const double ratio { bbox.width() / bbox.height() };
219  const int defaultHeight { static_cast<int>( 800 / ratio ) };
220  if ( parameters.width().isEmpty() && parameters.srcWidth().isEmpty() )
221  {
222  parameters.set( QgsWmsParameter::SRCWIDTH, 800 );
223  }
224  if ( parameters.height().isEmpty() && parameters.srcHeight().isEmpty() )
225  {
226  parameters.set( QgsWmsParameter::SRCHEIGHT, defaultHeight );
227  }
228  }
229  }
230 
231  QgsLayerTreeModel *legendModel( const QgsWmsRenderContext &context, QgsLayerTree &tree )
232  {
233 
234  const QgsWmsParameters parameters = context.parameters();
235  std::unique_ptr<QgsLayerTreeModel> model( new QgsLayerTreeModel( &tree ) );
236  std::unique_ptr<QgsMapSettings> mapSettings;
237 
238  if ( context.scaleDenominator() > 0 )
239  {
240  model->setLegendFilterByScale( context.scaleDenominator() );
241  }
242 
243  // content based legend
244  if ( ! parameters.bbox().isEmpty() )
245  {
246  mapSettings = std::make_unique<QgsMapSettings>();
247  mapSettings->setOutputSize( context.mapSize() );
248  // Inverted axis?
249  QgsRectangle bbox { parameters.bboxAsRectangle() };
250  const QString crs = parameters.crs();
251  if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
252  {
253  bbox.invert();
254  }
256  if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
258  {
259  bbox.invert();
260  }
261  mapSettings->setDestinationCrs( outputCrs );
262  mapSettings->setExtent( bbox );
263  QgsRenderer renderer( context );
264  QList<QgsMapLayer *> layers = context.layersToRender();
265  renderer.configureLayers( layers, mapSettings.get() );
266  mapSettings->setLayers( context.layersToRender() );
267  model->setLegendFilterByMap( mapSettings.get() );
268  }
269 
270  // if legend is not based on rendering rules
271  if ( parameters.rule().isEmpty() )
272  {
273  const QList<QgsLayerTreeNode *> children = tree.children();
274  const QString ruleLabel = parameters.ruleLabel();
275  for ( QgsLayerTreeNode *node : children )
276  {
277  if ( ! QgsLayerTree::isLayer( node ) )
278  continue;
279 
280  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
281 
282  // layer titles - hidden or not
283  QgsLegendRenderer::setNodeLegendStyle( nodeLayer, parameters.layerTitleAsBool() ? QgsLegendStyle::Subgroup : QgsLegendStyle::Hidden );
284  // rule item titles
285  if ( !parameters.ruleLabelAsBool() )
286  {
287  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
288  {
289  // empty string = no override, so let's use one space
290  legendNode->setUserLabel( QStringLiteral( " " ) );
291  }
292  }
293  else if ( ruleLabel.compare( QStringLiteral( "AUTO" ), Qt::CaseInsensitive ) == 0 )
294  {
295  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
296  {
297  //clearing label for single symbol
300  }
301  }
302  }
303  }
304 
305  return model.release();
306  }
307 
308  QgsLayerTree *layerTree( const QgsWmsRenderContext &context )
309  {
310  std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
311 
312  QList<QgsVectorLayerFeatureCounter *> counters;
313  for ( QgsMapLayer *ml : context.layersToRender() )
314  {
315  QgsLayerTreeLayer *lt = tree->addLayer( ml );
316  lt->setUseLayerName( false ); // do not modify underlying layer
317 
318  // name
319  if ( !ml->title().isEmpty() )
320  lt->setName( ml->title() );
321 
322  // show feature count
323  const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
324  const QString property = QStringLiteral( "showFeatureCount" );
325  lt->setCustomProperty( property, showFeatureCount );
326 
327  if ( ml->type() != QgsMapLayerType::VectorLayer || !showFeatureCount )
328  continue;
329 
330  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
332  if ( !counter )
333  continue;
334 
335  counters.append( counter );
336  }
337 
338  for ( QgsVectorLayerFeatureCounter *counter : counters )
339  {
340  counter->waitForFinished();
341  }
342 
343  return tree.release();
344  }
345 
346  QgsLayerTreeModelLegendNode *legendNode( const QString &rule, QgsLayerTreeModel &model )
347  {
348  for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
349  {
350  for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
351  {
352  if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
353  return node;
354  }
355  }
356  return nullptr;
357  }
358 } // namespace QgsWms
QgsVectorLayerFeatureCounter
Counts the features in a QgsVectorLayer in task. You should most likely not use this directly and ins...
Definition: qgsvectorlayerfeaturecounter.h:33
QgsWms::QgsServiceException::QGIS_InvalidParameterValue
@ QGIS_InvalidParameterValue
Definition: qgswmsserviceexception.h:75
qgswmsrenderer.h
QgsWms::parseImageFormat
ImageOutputFormat parseImageFormat(const QString &format)
Parse image format parameter.
Definition: qgswmsutils.cpp:91
QgsLayerTreeGroup::findLayers
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Definition: qgslayertreegroup.cpp:249
QgsWms::writeGetLegendGraphics
void writeGetLegendGraphics(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response)
Output GetLegendGRaphics response.
Definition: qgswmsgetlegendgraphics.cpp:57
QgsLegendRenderer::setNodeLegendStyle
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
Definition: qgslegendrenderer.cpp:948
QgsException
Defines a QGIS exception class.
Definition: qgsexception.h:34
QgsLayerTreeNode
This class is a base class for nodes in a layer tree.
Definition: qgslayertreenode.h:75
outputCrs
const QgsCoordinateReferenceSystem & outputCrs
Definition: qgswfsgetfeature.cpp:115
QgsLayerTreeModelLegendNode::isEmbeddedInParent
virtual bool isEmbeddedInParent() const
Definition: qgslayertreemodellegendnode.h:116
QgsLayerTreeModelLegendNode::data
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
QgsMapLayerType::VectorLayer
@ VectorLayer
Vector layer.
qgswmsutils.h
QgsWms::layerTree
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
Definition: qgswmsgetlegendgraphics.cpp:325
crs
const QgsCoordinateReferenceSystem & crs
Definition: qgswfsgetfeature.cpp:105
QgsCoordinateReferenceSystem::fromOgcWmsCrs
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
Definition: qgscoordinatereferencesystem.cpp:195
QgsLayerTreeLayer::setUseLayerName
void setUseLayerName(bool use=true)
Uses the layer's name if use is true, or the name manually set if false.
Definition: qgslayertreelayer.cpp:206
QgsWms::QgsWmsParameters::Format
Format
Output format for the response.
Definition: qgswmsparameters.h:353
qgssymbollayerutils.h
QgsWms::writeImage
void writeImage(QgsServerResponse &response, QImage &img, const QString &formatStr, int imageQuality)
Write image response.
Definition: qgswmsutils.cpp:128
QgsCoordinateReferenceSystem::hasAxisInverted
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
Definition: qgscoordinatereferencesystem.cpp:793
QgsLayerTree::toLayer
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:88
QgsLayerTreeModel
The QgsLayerTreeModel class is model implementation for Qt item views framework.
Definition: qgslayertreemodel.h:55
QgsWms::QgsServiceException::OGC_InvalidFormat
@ OGC_InvalidFormat
Definition: qgswmsserviceexception.h:62
qgsvectorlayerfeaturecounter.h
QgsServerInterface::accessControls
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:103
QgsServerResponse::write
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
Definition: qgsserverresponse.cpp:25
QgsWms::QgsWmsRenderContext::UseSrcWidthHeight
@ UseSrcWidthHeight
Definition: qgswmsrendercontext.h:63
QgsLayerTree
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
QgsWms::PNG8
@ PNG8
Definition: qgswmsutils.h:44
QgsServerCacheManager
A helper class that centralizes caches accesses given by all the server cache filter plugins.
Definition: qgsservercachemanager.h:41
QgsWms::PNG16
@ PNG16
Definition: qgswmsutils.h:45
QgsLayerTreeLayer::setName
void setName(const QString &n) override
Sets the layer's name.
Definition: qgslayertreelayer.cpp:86
QgsLayerTreeGroup::addLayer
QgsLayerTreeLayer * addLayer(QgsMapLayer *layer)
Append a new layer node for given map layer.
Definition: qgslayertreegroup.cpp:94
QgsLayerTreeLayer
Layer tree node points to a map layer.
Definition: qgslayertreelayer.h:43
QgsWms::PNG
@ PNG
Definition: qgswmsutils.h:43
QgsWms::legendModel
QgsLayerTreeModel * legendModel(const QgsWmsRenderContext &context, QgsLayerTree &tree)
Definition: qgswmsgetlegendgraphics.cpp:248
QgsWms::QgsServiceException::QGIS_MissingParameterValue
@ QGIS_MissingParameterValue
Definition: qgswmsserviceexception.h:74
QgsServerResponse::data
virtual QByteArray data() const =0
Gets the data written so far.
qgslayertree.h
QgsLayerTreeModelLegendNode::setEmbeddedInParent
virtual void setEmbeddedInParent(bool embedded)
Definition: qgslayertreemodellegendnode.h:117
QgsWms::QgsWmsParameter::BBOX
@ BBOX
Definition: qgswmsparameters.h:129
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsLayerTreeModel::rootGroup
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
Definition: qgslayertreemodel.cpp:492
qgswmsgetlegendgraphics.h
QgsWms::legendNode
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
Definition: qgswmsgetlegendgraphics.cpp:363
qgsvectorlayer.h
QgsAccessControl
A helper class that centralizes restrictions given by all the access control filter plugins.
Definition: qgsaccesscontrol.h:36
QgsWms
Median cut implementation.
Definition: qgsdxfwriter.cpp:22
QgsLayerTree::isLayer
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:66
QgsLayerTreeModelLegendNode::setUserLabel
virtual void setUserLabel(const QString &userLabel)
Definition: qgslayertreemodellegendnode.h:120
QgsWms::QgsWmsParameter::LAYERS
@ LAYERS
Definition: qgswmsparameters.h:144
QgsLayerTreeNode::setCustomProperty
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.
Definition: qgslayertreenode.cpp:208
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsServerInterface::cacheManager
virtual QgsServerCacheManager * cacheManager() const =0
Gets the registered server cache filters.
QgsServerCacheManager::getCachedImage
QByteArray getCachedImage(const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Returns cached image (or 0 if image not in cache) like tiles.
Definition: qgsservercachemanager.cpp:144
QgsWms::PNG1
@ PNG1
Definition: qgswmsutils.h:46
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsLayerTreeNode::children
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
Definition: qgslayertreenode.h:121
QgsWms::QgsWmsParameter::FORMAT
@ FORMAT
Definition: qgswmsparameters.h:163
qgsmaplayerlegend.h
QgsServerResponse::io
virtual QIODevice * io()=0
Returns the underlying QIODevice.
QgsWms::QgsWmsParameter::SRCHEIGHT
@ SRCHEIGHT
Definition: qgswmsparameters.h:201
QgsVectorLayer::countSymbolFeatures
QgsVectorLayerFeatureCounter * countSymbolFeatures(bool storeSymbolFids=false)
Count features for symbols.
Definition: qgsvectorlayer.cpp:827
QgsWms::QgsWmsParameter::SRCWIDTH
@ SRCWIDTH
Definition: qgswmsparameters.h:200
QgsServerCacheManager::setCachedImage
bool setCachedImage(const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Updates or inserts the image in cache like tiles.
Definition: qgsservercachemanager.cpp:161
QgsLegendStyle::Hidden
@ Hidden
Special style, item is hidden including margins around.
Definition: qgslegendstyle.h:69
qgswmsserviceexception.h
QgsLayerTreeModel::layerLegendNodes
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...
Definition: qgslayertreemodel.cpp:1572
QgsBadRequestException
Exception thrown in case of malformed request.
Definition: qgsserverexception.h:121
QgsWms::QgsWmsRenderContext::UseScaleDenominator
@ UseScaleDenominator
Definition: qgswmsrendercontext.h:53
qgswmsrequest.h
qgslegendrenderer.h
QgsLegendStyle::Subgroup
@ Subgroup
Legend subgroup title.
Definition: qgslegendstyle.h:72
QgsServerInterface
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins.
Definition: qgsserverinterface.h:60
QgsWms::checkParameters
void checkParameters(QgsWmsParameters &parameters)
checkParameters checks request parameters and sets SRCHEIGHT and SRCWIDTH to default values in case B...
Definition: qgswmsgetlegendgraphics.cpp:192
QgsProjectVersion
A class to describe the version of a project.
Definition: qgsprojectversion.h:32
QgsTask::waitForFinished
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
Definition: qgstaskmanager.cpp:168
QgsServerResponse
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
Definition: qgsserverresponse.h:43
QgsServerResponse::setHeader
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...
QgsLayerTreeModelLegendNode
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
Definition: qgslayertreemodellegendnode.h:49