QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 "qgswmsserviceexception.h"
31 #include "qgswmsrenderer.h"
32 
33 #include <QImage>
34 #include <QJsonObject>
35 #include <QJsonDocument>
36 
37 namespace QgsWms
38 {
39  void writeGetLegendGraphics( QgsServerInterface *serverIface, const QgsProject *project,
40  const QString &, const QgsServerRequest &request,
41  QgsServerResponse &response )
42  {
43  // get parameters from query
44  QgsWmsParameters parameters( QUrlQuery( request.url() ) );
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 
56  // get the requested output format
57  QgsWmsParameters::Format format = parameters.format();
58 
59  // parameters.format() returns NONE if the requested format is image/png with a
60  // mode (e.g. image/png;mode=16bit), so in that case we use parseImageFormat to
61  // give the requested format another chance
62 
63  QString imageSaveFormat;
64  QString imageContentType;
65  if ( format == QgsWmsParameters::Format::PNG )
66  {
67  imageContentType = "image/png";
68  imageSaveFormat = "PNG";
69  }
70  else if ( format == QgsWmsParameters::Format::JPG )
71  {
72  imageContentType = "image/jpeg";
73  imageSaveFormat = "JPEG";
74  }
75  else if ( format == QgsWmsParameters::Format::NONE )
76  {
77  switch ( parseImageFormat( parameters.formatAsString() ) )
78  {
79  case PNG:
80  case PNG8:
81  case PNG16:
82  case PNG1:
84  imageContentType = "image/png";
85  imageSaveFormat = "PNG";
86  break;
87  default:
88  break;
89  }
90  }
91 
92  if ( format == QgsWmsParameters::Format::NONE )
93  {
95  QStringLiteral( "Output format '%1' is not supported in the GetLegendGraphic request" ).arg( parameters.formatAsString() ) );
96  }
97 
98  // Get cached image
99 #ifdef HAVE_SERVER_PYTHON_PLUGINS
100  QgsAccessControl *accessControl = serverIface->accessControls();
101  QgsServerCacheManager *cacheManager = serverIface->cacheManager();
102  if ( cacheManager && !imageSaveFormat.isEmpty() )
103  {
104  QImage image;
105  QByteArray content = cacheManager->getCachedImage( project, request, accessControl );
106  if ( !content.isEmpty() && image.loadFromData( content ) )
107  {
108  response.setHeader( QStringLiteral( "Content-Type" ), imageContentType );
109  image.save( response.io(), qPrintable( imageSaveFormat ) );
110  return;
111  }
112  }
113 #endif
114  QgsRenderer renderer( context );
115 
116  // retrieve legend settings and model
117  std::unique_ptr<QgsLayerTree> tree( layerTree( context ) );
118  std::unique_ptr<QgsLayerTreeModel> model( legendModel( context, *tree.get() ) );
119 
120  // rendering
121  if ( format == QgsWmsParameters::Format::JSON )
122  {
123  QJsonObject result;
124  if ( !parameters.rule().isEmpty() )
125  {
127  QStringLiteral( "RULE cannot be used with JSON format" ) );
128  }
129  else
130  {
131  result = renderer.getLegendGraphicsAsJson( *model.get() );
132  }
133  tree->clear();
134  response.setHeader( QStringLiteral( "Content-Type" ), parameters.formatAsString() );
135  QJsonDocument doc( result );
136  response.write( doc.toJson( QJsonDocument::Compact ) );
137  }
138  else
139  {
140  std::unique_ptr<QImage> result;
141  if ( !parameters.rule().isEmpty() )
142  {
143  QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
144  result.reset( renderer.getLegendGraphics( *node ) );
145  }
146  else
147  {
148  result.reset( renderer.getLegendGraphics( *model.get() ) );
149  }
150  tree->clear();
151  if ( result )
152  {
153  writeImage( response, *result, parameters.formatAsString(), context.imageQuality() );
154 #ifdef HAVE_SERVER_PYTHON_PLUGINS
155  if ( cacheManager )
156  {
157  QByteArray content = response.data();
158  if ( !content.isEmpty() )
159  cacheManager->setCachedImage( &content, project, request, accessControl );
160  }
161 #endif
162  }
163  else
164  {
165  throw QgsException( QStringLiteral( "Failed to compute GetLegendGraphics image" ) );
166  }
167  }
168  }
169 
170  void checkParameters( QgsWmsParameters &parameters )
171  {
172  if ( parameters.allLayersNickname().isEmpty() )
173  {
175  parameters[QgsWmsParameter::LAYERS] );
176  }
177 
178  if ( parameters.format() == QgsWmsParameters::Format::NONE )
179  {
181  parameters[QgsWmsParameter::FORMAT] );
182  }
183 
184  if ( ! parameters.bbox().isEmpty() && !parameters.rule().isEmpty() )
185  {
187  QStringLiteral( "BBOX parameter cannot be combined with RULE." ) );
188  }
189 
190  if ( ! parameters.bbox().isEmpty() && parameters.bboxAsRectangle().isEmpty() )
191  {
193  parameters[QgsWmsParameter::BBOX] );
194  }
195  // If we have a contextual legend (BBOX is set)
196  // make sure (SRC)WIDTH and (SRC)HEIGHT are set, default to 800px width
197  // height is calculated from that value, respecting the aspect
198  if ( ! parameters.bbox().isEmpty() )
199  {
200  // Calculate ratio from bbox
201  QgsRectangle bbox { parameters.bboxAsRectangle() };
202  QString crs = parameters.crs();
203  if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
204  {
205  bbox.invert();
206  }
208  if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
209  outputCrs.hasAxisInverted() )
210  {
211  bbox.invert();
212  }
213  const double ratio { bbox.width() / bbox.height() };
214  int defaultHeight { static_cast<int>( 800 / ratio ) };
215  if ( parameters.width().isEmpty() && parameters.srcWidth().isEmpty() )
216  {
217  parameters.set( QgsWmsParameter::SRCWIDTH, 800 );
218  }
219  if ( parameters.height().isEmpty() && parameters.srcHeight().isEmpty() )
220  {
221  parameters.set( QgsWmsParameter::SRCHEIGHT, defaultHeight );
222  }
223  }
224  }
225 
227  {
228 
229  const QgsWmsParameters parameters = context.parameters();
230  std::unique_ptr<QgsLayerTreeModel> model( new QgsLayerTreeModel( &tree ) );
231  std::unique_ptr<QgsMapSettings> mapSettings;
232 
233  if ( context.scaleDenominator() > 0 )
234  {
235  model->setLegendFilterByScale( context.scaleDenominator() );
236  }
237 
238  // content based legend
239  if ( ! parameters.bbox().isEmpty() )
240  {
241  mapSettings = qgis::make_unique<QgsMapSettings>();
242  mapSettings->setOutputSize( context.mapSize() );
243  // Inverted axis?
244  QgsRectangle bbox { parameters.bboxAsRectangle() };
245  QString crs = parameters.crs();
246  if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
247  {
248  bbox.invert();
249  }
251  if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
252  outputCrs.hasAxisInverted() )
253  {
254  bbox.invert();
255  }
256  mapSettings->setDestinationCrs( outputCrs );
257  mapSettings->setExtent( bbox );
258  QgsRenderer renderer( context );
259  QList<QgsMapLayer *> layers = context.layersToRender();
260  renderer.configureLayers( layers, mapSettings.get() );
261  mapSettings->setLayers( context.layersToRender() );
262  model->setLegendFilterByMap( mapSettings.get() );
263  }
264 
265  // if legend is not based on rendering rules
266  if ( parameters.rule().isEmpty() )
267  {
268  QList<QgsLayerTreeNode *> children = tree.children();
269  for ( QgsLayerTreeNode *node : children )
270  {
271  if ( ! QgsLayerTree::isLayer( node ) )
272  continue;
273 
274  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
275 
276  // layer titles - hidden or not
278  // rule item titles
279  if ( !parameters.ruleLabelAsBool() )
280  {
281  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
282  {
283  // empty string = no override, so let's use one space
284  legendNode->setUserLabel( QStringLiteral( " " ) );
285  }
286  }
287  else if ( !parameters.layerTitleAsBool() && model->layerLegendNodes( nodeLayer, true ).count() )
288  {
289  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
290  {
293  }
294  }
295  }
296  }
297 
298  return model.release();
299  }
300 
302  {
303  std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
304 
305  QList<QgsVectorLayerFeatureCounter *> counters;
306  for ( QgsMapLayer *ml : context.layersToRender() )
307  {
308  QgsLayerTreeLayer *lt = tree->addLayer( ml );
309  lt->setUseLayerName( false ); // do not modify underlying layer
310 
311  // name
312  if ( !ml->title().isEmpty() )
313  lt->setName( ml->title() );
314 
315  // show feature count
316  const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
317  const QString property = QStringLiteral( "showFeatureCount" );
318  lt->setCustomProperty( property, showFeatureCount );
319 
320  if ( ml->type() != QgsMapLayerType::VectorLayer || !showFeatureCount )
321  continue;
322 
323  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
325  if ( !counter )
326  continue;
327 
328  counters.append( counter );
329  }
330 
331  for ( QgsVectorLayerFeatureCounter *counter : counters )
332  {
333  counter->waitForFinished();
334  }
335 
336  return tree.release();
337  }
338 
340  {
341  for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
342  {
343  for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
344  {
345  if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
346  return node;
347  }
348  }
349  return nullptr;
350  }
351 } // namespace QgsWms
352 
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
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...
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Base class for all map layer types.
Definition: qgsmaplayer.h:79
QList< QgsMapLayer * > layersToRender() const
Returns a list of all layers to actually render according to the current configuration.
void setUseLayerName(bool use=true)
Uses the layer&#39;s name if use is true, or the name manually set if false.
QgsWmsParameters parameters() const
Returns WMS parameters.
QgsRectangle bboxAsRectangle() const
Returns BBOX as a rectangle if defined and valid.
bool setCachedImage(const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Updates or inserts the image in cache like tiles.
QString srcWidth() const
Returns SRCWIDTH parameter or an empty string if not defined.
Counts the features in a QgsVectorLayer in task.
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
Format
Output format for the response.
Exception thrown in case of malformed request.
const QgsCoordinateReferenceSystem & crs
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device...
int imageQuality() const
Returns the image quality to use for rendering according to the current configuration.
void setFlag(Flag flag, bool on=true)
Sets or unsets a rendering flag according to the on value.
void setParameters(const QgsWmsParameters &parameters)
Sets WMS parameters.
QString bbox() const
Returns BBOX if defined or an empty string.
bool ruleLabelAsBool() const
Returns RULELABEL as a bool.
bool showFeatureCountAsBool() const
Returns SHOWFEATURECOUNT as a bool.
void writeImage(QgsServerResponse &response, QImage &img, const QString &formatStr, int imageQuality)
Write image response.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QStringList allLayersNickname() const
Returns nickname of layers found in LAYER and LAYERS parameters.
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:32
QString crs() const
Returns CRS or an empty string if none is defined.
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:426
QJsonObject getLegendGraphicsAsJson(QgsLayerTreeModel &model)
Returns the map legend as a JSON object.
A class to describe the version of a project.
Provides an interface to retrieve and manipulate WMS parameters received from the client...
void configureLayers(QList< QgsMapLayer *> &layers, QgsMapSettings *settings=nullptr)
Configures layers for rendering optionally considering the map settings.
A helper class that centralizes caches accesses given by all the server cache filter plugins...
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
This class is a base class for nodes in a layer tree.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:91
QgsProjectVersion versionAsNumber() const
Returns VERSION parameter if defined or its default value.
Legend subgroup title.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
Format format() const
Returns format.
void writeGetLegendGraphics(QgsServerInterface *serverIface, const QgsProject *project, const QString &, const QgsServerRequest &request, QgsServerResponse &response)
Output GetLegendGRaphics response.
void set(QgsWmsParameter::Name name, const QVariant &value)
Sets a parameter value thanks to its name.
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 setName(const QString &n) override
Sets the layer&#39;s name.
bool layerTitleAsBool() const
Returns LAYERTITLE as a bool or its default value if not defined.
Median cut implementation.
double scaleDenominator() const
Returns the scale denominator to use for rendering according to the current configuration.
virtual QByteArray data() const =0
Gets the data written so far.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins...
Special style, item is hidden including margins around.
virtual void setEmbeddedInParent(bool embedded)
Map renderer for WMS requests.
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
QgsLayerTreeModel * legendModel(const QgsWmsRenderContext &context, QgsLayerTree &tree)
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
virtual QIODevice * io()=0
Returns the underlying QIODevice.
Rendering context for the WMS renderer.
QString width() const
Returns WIDTH parameter or an empty string if not defined.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
This class represents a coordinate reference system (CRS).
virtual QgsServerCacheManager * cacheManager() const =0
Gets the registered server cache filters.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
const QgsCoordinateReferenceSystem & outputCrs
QString formatAsString() const
Returns FORMAT parameter as a string.
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...
A helper class that centralizes restrictions given by all the access control filter plugins...
QString height() const
Returns HEIGHT parameter or an empty string if not defined.
QString rule() const
Returns RULE parameter or an empty string if none is defined.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
void checkParameters(QgsWmsParameters &parameters)
checkParameters checks request parameters and sets SRCHEIGHT and SRCWIDTH to default values in case B...
QgsVectorLayerFeatureCounter * countSymbolFeatures(bool storeSymbolFids=false)
Count features for symbols.
QString srcHeight() const
Returns SRCHEIGHT parameter or an empty string if not defined.
Represents a vector layer which manages a vector based data sets.
Defines a QGIS exception class.
Definition: qgsexception.h:34
ImageOutputFormat parseImageFormat(const QString &format)
Parse image format parameter.
Definition: qgswmsutils.cpp:70
virtual void setUserLabel(const QString &userLabel)
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...
Layer tree node points to a map layer.
QImage * getLegendGraphics(QgsLayerTreeModel &model)
Returns the map legend as an image (or nullptr in case of error).
QByteArray getCachedImage(const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Returns cached image (or 0 if image not in cache) like tiles.