QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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 );
53  context.setFlag( QgsWmsRenderContext::UseSrcWidthHeight );
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 ) &&
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 
226  QgsLayerTreeModel *legendModel( const QgsWmsRenderContext &context, QgsLayerTree &tree )
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 ) &&
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  QString ruleLabel = parameters.ruleLabel();
270  for ( QgsLayerTreeNode *node : children )
271  {
272  if ( ! QgsLayerTree::isLayer( node ) )
273  continue;
274 
275  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
276 
277  // layer titles - hidden or not
278  QgsLegendRenderer::setNodeLegendStyle( nodeLayer, parameters.layerTitleAsBool() ? QgsLegendStyle::Subgroup : QgsLegendStyle::Hidden );
279  // rule item titles
280  if ( !parameters.ruleLabelAsBool() )
281  {
282  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
283  {
284  // empty string = no override, so let's use one space
285  legendNode->setUserLabel( QStringLiteral( " " ) );
286  }
287  }
288  else if ( ruleLabel.compare( QStringLiteral( "AUTO" ), Qt::CaseInsensitive ) == 0 )
289  {
290  for ( QgsLayerTreeModelLegendNode *legendNode : model->layerLegendNodes( nodeLayer ) )
291  {
292  //clearing label for single symbol
295  }
296  }
297  }
298  }
299 
300  return model.release();
301  }
302 
303  QgsLayerTree *layerTree( const QgsWmsRenderContext &context )
304  {
305  std::unique_ptr<QgsLayerTree> tree( new QgsLayerTree() );
306 
307  QList<QgsVectorLayerFeatureCounter *> counters;
308  for ( QgsMapLayer *ml : context.layersToRender() )
309  {
310  QgsLayerTreeLayer *lt = tree->addLayer( ml );
311  lt->setUseLayerName( false ); // do not modify underlying layer
312 
313  // name
314  if ( !ml->title().isEmpty() )
315  lt->setName( ml->title() );
316 
317  // show feature count
318  const bool showFeatureCount = context.parameters().showFeatureCountAsBool();
319  const QString property = QStringLiteral( "showFeatureCount" );
320  lt->setCustomProperty( property, showFeatureCount );
321 
322  if ( ml->type() != QgsMapLayerType::VectorLayer || !showFeatureCount )
323  continue;
324 
325  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
327  if ( !counter )
328  continue;
329 
330  counters.append( counter );
331  }
332 
333  for ( QgsVectorLayerFeatureCounter *counter : counters )
334  {
335  counter->waitForFinished();
336  }
337 
338  return tree.release();
339  }
340 
341  QgsLayerTreeModelLegendNode *legendNode( const QString &rule, QgsLayerTreeModel &model )
342  {
343  for ( QgsLayerTreeLayer *layer : model.rootGroup()->findLayers() )
344  {
345  for ( QgsLayerTreeModelLegendNode *node : model.layerLegendNodes( layer ) )
346  {
347  if ( node->data( Qt::DisplayRole ).toString().compare( rule ) == 0 )
348  return node;
349  }
350  }
351  return nullptr;
352  }
353 } // namespace QgsWms
QgsVectorLayerFeatureCounter
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:87
QgsLayerTreeGroup::findLayers
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Definition: qgslayertreegroup.cpp:223
QgsLegendRenderer::setNodeLegendStyle
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
Sets the style of a node.
Definition: qgslegendrenderer.cpp:880
QgsException
Definition: qgsexception.h:34
QgsLayerTreeNode
Definition: qgslayertreenode.h:74
outputCrs
const QgsCoordinateReferenceSystem & outputCrs
Definition: qgswfsgetfeature.cpp:115
QgsLayerTreeModelLegendNode::isEmbeddedInParent
virtual bool isEmbeddedInParent() const
Definition: qgslayertreemodellegendnode.h:84
QgsLayerTreeModelLegendNode::data
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
QgsMapLayerType::VectorLayer
@ VectorLayer
qgswmsutils.h
QgsWms::layerTree
QgsLayerTree * layerTree(const QgsWmsRenderContext &context)
Definition: qgswmsgetlegendgraphics.cpp:320
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:200
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:343
qgssymbollayerutils.h
QgsWms::writeImage
void writeImage(QgsServerResponse &response, QImage &img, const QString &formatStr, int imageQuality)
Write image response.
Definition: qgswmsutils.cpp:124
QgsServerRequest
Definition: qgsserverrequest.h:38
QgsCoordinateReferenceSystem::hasAxisInverted
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
Definition: qgscoordinatereferencesystem.cpp:808
QgsLayerTree::toLayer
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:88
QgsLayerTreeModel
Definition: qgslayertreemodel.h:53
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
Definition: qgsrectangle.h:41
QgsProject
Definition: qgsproject.h:92
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
Definition: qgslayertree.h:32
QgsWms::PNG8
@ PNG8
Definition: qgswmsutils.h:43
QgsServerCacheManager
A helper class that centralizes caches accesses given by all the server cache filter plugins.
Definition: qgsservercachemanager.h:40
QgsWms::PNG16
@ PNG16
Definition: qgswmsutils.h:44
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:82
QgsLayerTreeLayer
Definition: qgslayertreelayer.h:43
QgsWms::PNG
@ PNG
Definition: qgswmsutils.h:42
QgsWms::legendModel
QgsLayerTreeModel * legendModel(const QgsWmsRenderContext &context, QgsLayerTree &tree)
Definition: qgswmsgetlegendgraphics.cpp:243
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:85
QgsWms::QgsWmsParameter::BBOX
@ BBOX
Definition: qgswmsparameters.h:125
QgsCoordinateReferenceSystem
Definition: qgscoordinatereferencesystem.h:206
QgsLayerTreeModel::rootGroup
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
Definition: qgslayertreemodel.cpp:518
qgswmsgetlegendgraphics.h
QgsWms::legendNode
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
Definition: qgswmsgetlegendgraphics.cpp:358
qgsvectorlayer.h
QgsAccessControl
A helper class that centralizes restrictions given by all the access control filter plugins.
Definition: qgsaccesscontrol.h:36
QgsWms
WMS 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:88
QgsWms::QgsWmsParameter::LAYERS
@ LAYERS
Definition: qgswmsparameters.h:140
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:180
QgsVectorLayer
Definition: qgsvectorlayer.h:385
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:139
QgsWms::PNG1
@ PNG1
Definition: qgswmsutils.h:45
QgsMapLayer
Definition: qgsmaplayer.h:81
QgsWms::writeGetLegendGraphics
void writeGetLegendGraphics(QgsServerInterface *serverIface, const QgsProject *project, const QString &, const QgsServerRequest &request, QgsServerResponse &response)
Output GetLegendGRaphics response.
Definition: qgswmsgetlegendgraphics.cpp:56
QgsLayerTreeNode::children
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
Definition: qgslayertreenode.h:112
QgsWms::QgsWmsParameter::FORMAT
@ FORMAT
Definition: qgswmsparameters.h:159
qgsmaplayerlegend.h
QgsServerResponse::io
virtual QIODevice * io()=0
Returns the underlying QIODevice.
QgsWms::QgsWmsParameter::SRCHEIGHT
@ SRCHEIGHT
Definition: qgswmsparameters.h:193
QgsServerRequest::url
QUrl url() const
Definition: qgsserverrequest.cpp:65
QgsVectorLayer::countSymbolFeatures
QgsVectorLayerFeatureCounter * countSymbolFeatures(bool storeSymbolFids=false)
Count features for symbols.
Definition: qgsvectorlayer.cpp:766
QgsWms::QgsWmsParameter::SRCWIDTH
@ SRCWIDTH
Definition: qgswmsparameters.h:192
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:156
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:1533
QgsBadRequestException
Exception thrown in case of malformed request.
Definition: qgsserverexception.h:121
QgsWms::QgsWmsRenderContext::UseScaleDenominator
@ UseScaleDenominator
Definition: qgswmsrendercontext.h:53
qgslegendrenderer.h
QgsLegendStyle::Subgroup
@ Subgroup
Legend subgroup title.
Definition: qgslegendstyle.h:72
QgsServerInterface
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:187
QgsProjectVersion
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
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
Definition: qgslayertreemodellegendnode.h:50