QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
35 namespace QgsWms
36 {
37  void writeGetLegendGraphics( QgsServerInterface *serverIface, const QgsProject *project,
38  const QString &, const QgsServerRequest &request,
39  QgsServerResponse &response )
40  {
41  // get parameters from query
42  QgsWmsParameters parameters( QUrlQuery( request.url() ) );
43 
44  // check parameters validity
45  checkParameters( parameters );
46 
47  // init render context
48  QgsWmsRenderContext context( project, serverIface );
51  context.setParameters( parameters );
52 
53  const QString format = request.parameters().value( QStringLiteral( "FORMAT" ), QStringLiteral( "PNG" ) );
54  ImageOutputFormat outputFormat = parseImageFormat( format );
55 
56  QString saveFormat;
57  QString contentType;
58  switch ( outputFormat )
59  {
60  case PNG:
61  case PNG8:
62  case PNG16:
63  case PNG1:
64  contentType = "image/png";
65  saveFormat = "PNG";
66  break;
67  case JPEG:
68  contentType = "image/jpeg";
69  saveFormat = "JPEG";
70  break;
71  default:
72  throw QgsServiceException( "InvalidFormat",
73  QStringLiteral( "Output format '%1' is not supported in the GetLegendGraphic request" ).arg( format ) );
74  break;
75  }
76 
77  // Get cached image
78 #ifdef HAVE_SERVER_PYTHON_PLUGINS
79  QgsAccessControl *accessControl = serverIface->accessControls();
80  QgsServerCacheManager *cacheManager = serverIface->cacheManager();
81  if ( cacheManager )
82  {
83  QImage image;
84  QByteArray content = cacheManager->getCachedImage( project, request, accessControl );
85  if ( !content.isEmpty() && image.loadFromData( content ) )
86  {
87  response.setHeader( QStringLiteral( "Content-Type" ), contentType );
88  image.save( response.io(), qPrintable( saveFormat ) );
89  return;
90  }
91  }
92 #endif
93  QgsRenderer renderer( context );
94 
95  // retrieve legend settings and model
96  std::unique_ptr<QgsLayerTree> tree( layerTree( context ) );
97  std::unique_ptr<QgsLayerTreeModel> model( legendModel( context, *tree.get() ) );
98 
99  // rendering
100  std::unique_ptr<QImage> result;
101  if ( !parameters.rule().isEmpty() )
102  {
103  QgsLayerTreeModelLegendNode *node = legendNode( parameters.rule(), *model.get() );
104  result.reset( renderer.getLegendGraphics( *node ) );
105  }
106  else
107  {
108  result.reset( renderer.getLegendGraphics( *model.get() ) );
109  }
110 
111  tree->clear();
112 
113  if ( result )
114  {
115  writeImage( response, *result, format, context.imageQuality() );
116 #ifdef HAVE_SERVER_PYTHON_PLUGINS
117  if ( cacheManager )
118  {
119  QByteArray content = response.data();
120  if ( !content.isEmpty() )
121  cacheManager->setCachedImage( &content, project, request, accessControl );
122  }
123 #endif
124  }
125  else
126  {
127  throw QgsException( QStringLiteral( "Failed to compute GetLegendGraphics image" ) );
128  }
129  }
130 
131  void checkParameters( QgsWmsParameters &parameters )
132  {
133  if ( parameters.allLayersNickname().isEmpty() )
134  {
136  parameters[QgsWmsParameter::LAYERS] );
137  }
138 
139  if ( parameters.format() == QgsWmsParameters::Format::NONE )
140  {
142  parameters[QgsWmsParameter::FORMAT] );
143  }
144 
145  if ( ! parameters.bbox().isEmpty() && !parameters.rule().isEmpty() )
146  {
148  QStringLiteral( "BBOX parameter cannot be combined with RULE." ) );
149  }
150 
151  if ( ! parameters.bbox().isEmpty() && parameters.bboxAsRectangle().isEmpty() )
152  {
154  parameters[QgsWmsParameter::BBOX] );
155  }
156  // If we have a contextual legend (BBOX is set)
157  // make sure (SRC)WIDTH and (SRC)HEIGHT are set, default to 800px width
158  // height is calculated from that value, respecting the aspect
159  if ( ! parameters.bbox().isEmpty() )
160  {
161  // Calculate ratio from bbox
162  QgsRectangle bbox { parameters.bboxAsRectangle() };
163  QString crs = parameters.crs();
164  if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
165  {
166  bbox.invert();
167  }
169  if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
170  outputCrs.hasAxisInverted() )
171  {
172  bbox.invert();
173  }
174  const double ratio { bbox.width() / bbox.height() };
175  int defaultHeight { static_cast<int>( 800 / ratio ) };
176  if ( parameters.width().isEmpty() && parameters.srcWidth().isEmpty() )
177  {
178  parameters.set( QgsWmsParameter::SRCWIDTH, 800 );
179  }
180  if ( parameters.height().isEmpty() && parameters.srcHeight().isEmpty() )
181  {
182  parameters.set( QgsWmsParameter::SRCHEIGHT, defaultHeight );
183  }
184  }
185  }
186 
188  {
189 
190  const QgsWmsParameters parameters = context.parameters();
191  std::unique_ptr<QgsLayerTreeModel> model( new QgsLayerTreeModel( &tree ) );
192  std::unique_ptr<QgsMapSettings> mapSettings;
193 
194  if ( context.scaleDenominator() > 0 )
195  {
196  model->setLegendFilterByScale( context.scaleDenominator() );
197  }
198 
199  // content based legend
200  if ( ! parameters.bbox().isEmpty() )
201  {
202  mapSettings = qgis::make_unique<QgsMapSettings>();
203  mapSettings->setOutputSize( context.mapSize() );
204  // Inverted axis?
205  QgsRectangle bbox { parameters.bboxAsRectangle() };
206  QString crs = parameters.crs();
207  if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
208  {
209  bbox.invert();
210  }
212  if ( parameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
213  outputCrs.hasAxisInverted() )
214  {
215  bbox.invert();
216  }
217  mapSettings->setDestinationCrs( outputCrs );
218  mapSettings->setExtent( bbox );
219  QgsRenderer renderer( context );
220  QList<QgsMapLayer *> layers = context.layersToRender();
221  renderer.configureLayers( layers, mapSettings.get() );
222  mapSettings->setLayers( context.layersToRender() );
223  model->setLegendFilterByMap( mapSettings.get() );
224  }
225 
226  // if legend is not based on rendering rules
227  if ( parameters.rule().isEmpty() )
228  {
229  QList<QgsLayerTreeNode *> children = tree.children();
230  for ( QgsLayerTreeNode *node : children )
231  {
232  if ( ! QgsLayerTree::isLayer( node ) )
233  continue;
234 
235  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
236 
237  // layer titles - hidden or not
239 
240  // rule item titles
241  if ( !parameters.ruleLabelAsBool() )
242  {
243  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
244  {
245  // empty string = no override, so let's use one space
246  legendNode->setUserLabel( QStringLiteral( " " ) );
247  }
248  }
249  else if ( !parameters.layerTitleAsBool() )
250  {
251  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
252  {
255  }
256  }
257  }
258  }
259 
260  return model.release();
261  }
262 
264  {
265  std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
266 
267  QList<QgsVectorLayerFeatureCounter *> counters;
268  for ( QgsMapLayer *ml : context.layersToRender() )
269  {
270  QgsLayerTreeLayer *lt = tree->addLayer( ml );
271  lt->setUseLayerName( false ); // do not modify underlying layer
272 
273  // name
274  if ( !ml->title().isEmpty() )
275  lt->setName( ml->title() );
276 
277  // show feature count
278  const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
279  const QString property = QStringLiteral( "showFeatureCount" );
280  lt->setCustomProperty( property, showFeatureCount );
281 
282  if ( ml->type() != QgsMapLayerType::VectorLayer || !showFeatureCount )
283  continue;
284 
285  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
287  if ( !counter )
288  continue;
289 
290  counters.append( counter );
291  }
292 
293  for ( QgsVectorLayerFeatureCounter *counter : counters )
294  {
295  counter->waitForFinished();
296  }
297 
298  return tree.release();
299  }
300 
302  {
303  for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
304  {
305  for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
306  {
307  if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
308  return node;
309  }
310  }
311  return nullptr;
312  }
313 } // namespace QgsWms
314 
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.
QgsVectorLayerFeatureCounter * countSymbolFeatures()
Count features for symbols.
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.
Exception thrown in case of malformed request.
const QgsCoordinateReferenceSystem & crs
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.
Exception class for WMS service exceptions.
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
QgsServerRequest::Parameters parameters() const
Returns a map of query parameters with keys converted to uppercase.
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:89
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
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.
ImageOutputFormat
Supported image output format.
Definition: qgswmsutils.h:39
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...
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.