QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgswmsrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgswmsrenderer.cpp
3  -------------------
4  begin : May 14, 2006
5  copyright : (C) 2006 by Marco Hugentobler
6  (C) 2017 by David Marteau
7  email : marco dot hugentobler at karto dot baug dot ethz dot ch
8  david dot marteau at 3liz dot com
9  ***************************************************************************/
10 
11 /***************************************************************************
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * *
18  ***************************************************************************/
19 
20 
21 #include "qgswmsutils.h"
22 #include "qgswmsrenderer.h"
23 #include "qgsfilterrestorer.h"
24 #include "qgscapabilitiescache.h"
25 #include "qgsexception.h"
26 #include "qgsfields.h"
27 #include "qgsfieldformatter.h"
29 #include "qgsfeatureiterator.h"
30 #include "qgsgeometry.h"
31 #include "qgsmapserviceexception.h"
32 #include "qgslayertree.h"
33 #include "qgslayertreemodel.h"
35 #include "qgslegendrenderer.h"
36 #include "qgsmaplayer.h"
37 #include "qgsmaplayerlegend.h"
38 #include "qgsmaptopixel.h"
39 #include "qgsproject.h"
41 #include "qgsrasterlayer.h"
42 #include "qgsrasterrenderer.h"
43 #include "qgsscalecalculator.h"
45 #include "qgsvectordataprovider.h"
46 #include "qgsvectorlayer.h"
47 #include "qgslogger.h"
48 #include "qgsmessagelog.h"
49 #include "qgssymbol.h"
50 #include "qgsrenderer.h"
51 #include "qgspaintenginehack.h"
52 #include "qgsogcutils.h"
53 #include "qgsfeature.h"
54 #include "qgsaccesscontrol.h"
55 #include "qgsfeaturerequest.h"
56 #include "qgsmaprendererjobproxy.h"
57 #include "qgswmsserviceexception.h"
58 #include "qgsserverprojectutils.h"
59 #include "qgsserverfeatureid.h"
61 #include "qgswkbtypes.h"
62 #include "qgsannotationmanager.h"
63 #include "qgsannotation.h"
64 #include "qgsvectorlayerlabeling.h"
66 #include "qgspallabeling.h"
67 #include "qgslayerrestorer.h"
68 #include "qgsdxfexport.h"
69 #include "qgssymbollayerutils.h"
70 #include "qgsserverexception.h"
71 #include "qgsfeaturestore.h"
72 
73 #include <QImage>
74 #include <QPainter>
75 #include <QStringList>
76 #include <QTemporaryFile>
77 #include <QTextStream>
78 #include <QDir>
79 
80 //for printing
81 #include "qgslayoutmanager.h"
82 #include "qgslayoutexporter.h"
83 #include "qgslayoutsize.h"
84 #include "qgslayoutrendercontext.h"
85 #include "qgslayoutmeasurement.h"
86 #include "qgsprintlayout.h"
88 #include "qgslayoutitempage.h"
89 #include "qgslayoutitemlabel.h"
90 #include "qgslayoutitemlegend.h"
91 #include "qgslayoutitemmap.h"
92 #include "qgslayoutitemmapgrid.h"
93 #include "qgslayoutframe.h"
94 #include "qgslayoutitemhtml.h"
95 #include "qgslayoutitempicture.h"
96 #include "qgslayoutitemscalebar.h"
97 #include "qgslayoutitemshape.h"
99 #include "qgsogcutils.h"
100 #include "qgsunittypes.h"
101 #include <QBuffer>
102 #include <QPrinter>
103 #include <QSvgGenerator>
104 #include <QUrl>
105 #include <QPaintEngine>
106 
107 namespace QgsWms
108 {
109 
110  namespace
111  {
112 
113  QgsLayerTreeModelLegendNode *_findLegendNodeForRule( QgsLayerTreeModel *legendModel, const QString &rule )
114  {
115  for ( QgsLayerTreeLayer *nodeLayer : legendModel->rootGroup()->findLayers() )
116  {
117  for ( QgsLayerTreeModelLegendNode *legendNode : legendModel->layerLegendNodes( nodeLayer ) )
118  {
119  if ( legendNode->data( Qt::DisplayRole ).toString() == rule )
120  return legendNode;
121  }
122  }
123  return nullptr;
124  }
125 
126  } // namespace
127 
128 
130  const QgsProject *project,
131  QgsWmsParameters &parameters )
132  : mWmsParameters( parameters )
133 #ifdef HAVE_SERVER_PYTHON_PLUGINS
134  , mAccessControl( serverIface->accessControls() )
135 #endif
136  , mSettings( *serverIface->serverSettings() )
137  , mProject( project )
138  {
139  mWmsParameters.dump();
140 
141  initRestrictedLayers();
142  initNicknameLayers();
143  }
144 
146  {
147  removeTemporaryLayers();
148  }
149 
150 
152  {
153  // check parameters
154  if ( mWmsParameters.allLayersNickname().isEmpty() )
155  throw QgsBadRequestException( QStringLiteral( "LayerNotSpecified" ),
156  QStringLiteral( "LAYER is mandatory for GetLegendGraphic operation" ) );
157 
158  if ( mWmsParameters.format() == QgsWmsParameters::Format::NONE )
159  throw QgsBadRequestException( QStringLiteral( "FormatNotSpecified" ),
160  QStringLiteral( "FORMAT is mandatory for GetLegendGraphic operation" ) );
161 
162  // Temporary workaround for backport of https://github.com/qgis/QGIS/issues/31846
163  // Since 3.8 we have a better approach to parameters checking
164  if ( ! mWmsParameters.bbox().isEmpty() )
165  {
166  if ( mWmsParameters.width().isEmpty() && mWmsParameters.srcWidth().isEmpty() )
167  {
168  mWmsParameters.mWmsParameters.insert( QgsWmsParameter::SRCWIDTH, QgsWmsParameter( QgsWmsParameter::SRCWIDTH, QVariant::Int, 800 ) );
169  }
170  if ( mWmsParameters.height().isEmpty() && mWmsParameters.srcHeight().isEmpty() )
171  {
172  mWmsParameters.mWmsParameters.insert( QgsWmsParameter::SRCHEIGHT, QgsWmsParameter( QgsWmsParameter::SRCHEIGHT, QVariant::Int, 600 ) );
173  }
174  }
175 
176  double scaleDenominator = -1;
177  if ( ! mWmsParameters.scale().isEmpty() )
178  scaleDenominator = mWmsParameters.scaleAsDouble();
179 
180  QgsLegendSettings legendSettings = mWmsParameters.legendSettings();
181 
182  // get layers
183  std::unique_ptr<QgsLayerRestorer> restorer;
184  restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
185 
186  QList<QgsMapLayer *> layers;
187  QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
188 
189  QString sld = mWmsParameters.sldBody();
190  if ( !sld.isEmpty() )
191  layers = sldStylizedLayers( sld );
192  else
193  layers = stylizedLayers( params );
194 
195  removeUnwantedLayers( layers, scaleDenominator );
196  std::reverse( layers.begin(), layers.end() );
197 
198  // check permissions
199  for ( QgsMapLayer *ml : layers )
200  checkLayerReadPermissions( ml );
201 
202  // build layer tree model for legend
203  QgsLayerTree rootGroup;
204  std::unique_ptr<QgsLayerTreeModel> legendModel;
205  legendModel.reset( buildLegendTreeModel( layers, scaleDenominator, rootGroup ) );
206 
207  // rendering step
208  qreal dpmm = dotsPerMm();
209  std::unique_ptr<QImage> image;
210  std::unique_ptr<QPainter> painter;
211 
212  // getting scale from bbox or default size
213  if ( !mWmsParameters.bbox().isEmpty() )
214  {
215  QgsMapSettings mapSettings;
216  std::unique_ptr<QImage> tmp( createImage( width(), height(), false ) );
217  configureMapSettings( tmp.get(), mapSettings );
218  legendSettings.setMapScale( mapSettings.scale() );
219  legendSettings.setMapUnitsPerPixel( mapSettings.mapUnitsPerPixel() );
220  }
221  else
222  {
223  double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject ) / dpmm;
224  legendSettings.setMapUnitsPerPixel( defaultMapUnitsPerPixel );
225  }
226 
227  if ( !mWmsParameters.rule().isEmpty() )
228  {
229  QString rule = mWmsParameters.rule();
230  int width = mWmsParameters.widthAsInt();
231  int height = mWmsParameters.heightAsInt();
232 
233  image.reset( createImage( width, height, false ) );
234  painter.reset( new QPainter( image.get() ) );
235  painter->setRenderHint( QPainter::Antialiasing, true );
236  painter->scale( dpmm, dpmm );
237 
238  QgsLayerTreeModelLegendNode *legendNode = _findLegendNodeForRule( legendModel.get(), rule );
239  if ( legendNode )
240  {
242  ctx.painter = painter.get();
243  ctx.labelXOffset = 0;
244  ctx.point = QPointF();
245  double itemHeight = height / dpmm;
246  legendNode->drawSymbol( legendSettings, &ctx, itemHeight );
247  painter->end();
248  }
249  }
250  else
251  {
252  QgsLegendRenderer legendRendererNew( legendModel.get(), legendSettings );
253 
254  QSizeF minSize = legendRendererNew.minimumSize();
255  QSize s( minSize.width() * dpmm, minSize.height() * dpmm );
256 
257  image.reset( createImage( s.width(), s.height(), false ) );
258  painter.reset( new QPainter( image.get() ) );
259  painter->setRenderHint( QPainter::Antialiasing, true );
260  painter->scale( dpmm, dpmm );
261 
262  legendRendererNew.drawLegend( painter.get() );
263  painter->end();
264  }
265 
266  rootGroup.clear();
267  return image.release();
268  }
269 
270  void QgsRenderer::runHitTest( const QgsMapSettings &mapSettings, HitTest &hitTest ) const
271  {
272  QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
273 
274  for ( const QString &id : mapSettings.layerIds() )
275  {
276  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mProject->mapLayer( id ) );
277  if ( !vl || !vl->renderer() )
278  continue;
279 
280  if ( vl->hasScaleBasedVisibility() && vl->isInScaleRange( mapSettings.scale() ) )
281  {
282  hitTest[vl] = SymbolSet(); // no symbols -> will not be shown
283  continue;
284  }
285 
286  QgsCoordinateTransform tr = mapSettings.layerTransform( vl );
287  context.setCoordinateTransform( tr );
289 
290  SymbolSet &usedSymbols = hitTest[vl];
291  runHitTestLayer( vl, usedSymbols, context );
292  }
293  }
294 
295  void QgsRenderer::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, QgsRenderContext &context ) const
296  {
297  std::unique_ptr< QgsFeatureRenderer > r( vl->renderer()->clone() );
298  bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
299  r->startRender( context, vl->fields() );
300  QgsFeature f;
301  QgsFeatureRequest request( context.extent() );
303  QgsFeatureIterator fi = vl->getFeatures( request );
304  while ( fi.nextFeature( f ) )
305  {
306  context.expressionContext().setFeature( f );
307  if ( moreSymbolsPerFeature )
308  {
309  for ( QgsSymbol *s : r->originalSymbolsForFeature( f, context ) )
310  usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) );
311  }
312  else
313  usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( r->originalSymbolForFeature( f, context ) ) );
314  }
315  r->stopRender( context );
316  }
317 
318 
319  QByteArray QgsRenderer::getPrint( const QString &formatString )
320  {
321  //GetPrint request needs a template parameter
322  QString templateName = mWmsParameters.composerTemplate();
323  if ( templateName.isEmpty() )
324  {
325  throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ),
326  QStringLiteral( "The TEMPLATE parameter is required for the GetPrint request" ) );
327  }
328 
329  // get layers parameters
330  QList<QgsMapLayer *> layers;
331  QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
332 
333  // create the output image (this is not really used but configureMapSettings
334  // needs it)
335  std::unique_ptr<QImage> image( new QImage() );
336 
337  // configure map settings (background, DPI, ...)
338  QgsMapSettings mapSettings;
339  configureMapSettings( image.get(), mapSettings );
340 
341  // init layer restorer before doing anything
342  std::unique_ptr<QgsLayerRestorer> restorer;
343  restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
344 
345  // init stylized layers according to LAYERS/STYLES or SLD
346  QString sld = mWmsParameters.sldBody();
347  if ( !sld.isEmpty() )
348  {
349  layers = sldStylizedLayers( sld );
350  }
351  else
352  {
353  layers = stylizedLayers( params );
354  }
355 
356  // remove unwanted layers (restricted layers, ...)
357  removeUnwantedLayers( layers );
358 
359  // configure each layer with opacity, selection filter, ...
360  bool updateMapExtent = mWmsParameters.bbox().isEmpty();
361  for ( QgsMapLayer *layer : layers )
362  {
363  checkLayerReadPermissions( layer );
364 
365  for ( const QgsWmsParametersLayer &param : params )
366  {
367  if ( param.mNickname == layerNickname( *layer ) )
368  {
369  setLayerOpacity( layer, param.mOpacity );
370 
371  setLayerFilter( layer, param.mFilter );
372 
373  setLayerSelection( layer, param.mSelection );
374 
375  if ( updateMapExtent )
376  updateExtent( layer, mapSettings );
377 
378  break;
379  }
380  }
381 
382  setLayerAccessControlFilter( layer );
383  }
384 
385  // add highlight layers above others
386  layers = layers << highlightLayers( mWmsParameters.highlightLayersParameters() );
387 
388  // add layers to map settings (revert order for the rendering)
389  std::reverse( layers.begin(), layers.end() );
390  mapSettings.setLayers( layers );
391 
392  const QgsLayoutManager *lManager = mProject->layoutManager();
393  QgsPrintLayout *sourceLayout( dynamic_cast<QgsPrintLayout *>( lManager->layoutByName( templateName ) ) );
394  if ( !sourceLayout )
395  {
396  throw QgsBadRequestException( QStringLiteral( "InvalidTemplate" ),
397  QStringLiteral( "Template '%1' is not known" ).arg( templateName ) );
398  }
399 
400  // Check that layout has at least one page
401  if ( sourceLayout->pageCollection()->pageCount() < 1 )
402  {
403  throw QgsBadRequestException( QStringLiteral( "InvalidTemplate" ),
404  QStringLiteral( "Template '%1' has no pages" ).arg( templateName ) );
405  }
406 
407  std::unique_ptr<QgsPrintLayout> layout( sourceLayout->clone() );
408 
409  configurePrintLayout( layout.get(), mapSettings );
410 
411  // Get the temporary output file
412  QTemporaryFile tempOutputFile( QDir::tempPath() + '/' + QStringLiteral( "XXXXXX.%1" ).arg( formatString.toLower() ) );
413  if ( !tempOutputFile.open() )
414  {
415  throw QgsServerException( QStringLiteral( "Could not open temporary file for the GetPrint request." ) );
416 
417  }
418 
419  if ( formatString.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
420  {
421  // Settings for the layout exporter
423  if ( !mWmsParameters.dpi().isEmpty() )
424  {
425  bool ok;
426  double dpi( mWmsParameters.dpi().toDouble( &ok ) );
427  if ( ok )
428  exportSettings.dpi = dpi;
429  }
430  // Draw selections
432  QgsLayoutExporter exporter( layout.get() );
433  exporter.exportToSvg( tempOutputFile.fileName(), exportSettings );
434  }
435  else if ( formatString.compare( QLatin1String( "png" ), Qt::CaseInsensitive ) == 0 || formatString.compare( QLatin1String( "jpg" ), Qt::CaseInsensitive ) == 0 )
436  {
437  // Settings for the layout exporter
439  // Get the dpi from input or use the default
440  double dpi( layout->renderContext().dpi( ) );
441  if ( !mWmsParameters.dpi().isEmpty() )
442  {
443  bool ok;
444  double _dpi = mWmsParameters.dpi().toDouble( &ok );
445  if ( ! ok )
446  dpi = _dpi;
447  }
448  exportSettings.dpi = dpi;
449  // Draw selections
451  // Destination image size in px
452  QgsLayoutSize layoutSize( layout->pageCollection()->page( 0 )->sizeWithUnits() );
453  QgsLayoutMeasurement width( layout->convertFromLayoutUnits( layoutSize.width(), QgsUnitTypes::LayoutUnit::LayoutMillimeters ) );
454  QgsLayoutMeasurement height( layout->convertFromLayoutUnits( layoutSize.height(), QgsUnitTypes::LayoutUnit::LayoutMillimeters ) );
455  exportSettings.imageSize = QSize( static_cast<int>( width.length() * dpi / 25.4 ), static_cast<int>( height.length() * dpi / 25.4 ) );
456  // Export first page only (unless it's a pdf, see below)
457  exportSettings.pages.append( 0 );
458  QgsLayoutExporter exporter( layout.get() );
459  exporter.exportToImage( tempOutputFile.fileName(), exportSettings );
460  }
461  else if ( formatString.compare( QLatin1String( "pdf" ), Qt::CaseInsensitive ) == 0 )
462  {
463  // Settings for the layout exporter
465  // TODO: handle size from input ?
466  if ( !mWmsParameters.dpi().isEmpty() )
467  {
468  bool ok;
469  double dpi( mWmsParameters.dpi().toDouble( &ok ) );
470  if ( ok )
471  exportSettings.dpi = dpi;
472  }
473  // Draw selections
475  // Print as raster
476  exportSettings.rasterizeWholeImage = layout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
477  // Export all pages
478  QgsLayoutExporter exporter( layout.get() );
479  exporter.exportToPdf( tempOutputFile.fileName(), exportSettings );
480  }
481  else //unknown format
482  {
483  throw QgsBadRequestException( QStringLiteral( "InvalidFormat" ),
484  QStringLiteral( "Output format '%1' is not supported in the GetPrint request" ).arg( formatString ) );
485  }
486 
487  return tempOutputFile.readAll();
488  }
489 
490  bool QgsRenderer::configurePrintLayout( QgsPrintLayout *c, const QgsMapSettings &mapSettings )
491  {
492  c->renderContext().setSelectionColor( mapSettings.selectionColor() );
493  // Maps are configured first
494  QList<QgsLayoutItemMap *> maps;
495  c->layoutItems<QgsLayoutItemMap>( maps );
496  // Layout maps now use a string UUID as "id", let's assume that the first map
497  // has id 0 and so on ...
498  int mapId = 0;
499  for ( const auto &map : qgis::as_const( maps ) )
500  {
501  QgsWmsParametersComposerMap cMapParams = mWmsParameters.composerMapParameters( mapId );
502  mapId++;
503 
504  //map extent is mandatory
505  if ( !cMapParams.mHasExtent )
506  {
507  //remove map from composition if not referenced by the request
508  c->removeLayoutItem( map );
509  continue;
510  }
511  // Change CRS of map set to "project CRS" to match requested CRS
512  // (if map has a valid preset crs then we keep this crs and don't use the
513  // requested crs for this map item)
514  if ( mapSettings.destinationCrs().isValid() && !map->presetCrs().isValid() )
515  map->setCrs( mapSettings.destinationCrs() );
516 
517  QgsRectangle r( cMapParams.mExtent );
518  if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
519  mapSettings.destinationCrs().hasAxisInverted() )
520  {
521  r.invert();
522  }
523  map->setExtent( r );
524 
525  // scale
526  if ( cMapParams.mScale > 0 )
527  {
528  map->setScale( cMapParams.mScale );
529  }
530 
531  // rotation
532  if ( cMapParams.mRotation )
533  {
534  map->setMapRotation( cMapParams.mRotation );
535  }
536 
537  if ( !map->keepLayerSet() )
538  {
539  if ( cMapParams.mLayers.isEmpty() )
540  {
541  map->setLayers( mapSettings.layers() );
542  }
543  else
544  {
545  QList<QgsMapLayer *> layerSet = stylizedLayers( cMapParams.mLayers );
546  layerSet << highlightLayers( cMapParams.mHighlightLayers );
547  std::reverse( layerSet.begin(), layerSet.end() );
548  map->setLayers( layerSet );
549  }
550  map->setKeepLayerSet( true );
551  }
552 
553  //grid space x / y
554  if ( cMapParams.mGridX > 0 && cMapParams.mGridY > 0 )
555  {
556  map->grid()->setIntervalX( cMapParams.mGridX );
557  map->grid()->setIntervalY( cMapParams.mGridY );
558  }
559  }
560 
561  // Labels
562  QList<QgsLayoutItemLabel *> labels;
563  c->layoutItems<QgsLayoutItemLabel>( labels );
564  for ( const auto &label : qgis::as_const( labels ) )
565  {
566  bool ok = false;
567  const QString labelId = label->id();
568  const QString labelParam = mWmsParameters.layoutParameter( labelId, ok );
569 
570  if ( !ok )
571  continue;
572 
573  if ( labelParam.isEmpty() )
574  {
575  //remove exported labels referenced in the request
576  //but with empty string
577  c->removeItem( label );
578  delete label;
579  continue;
580  }
581 
582  label->setText( labelParam );
583  }
584 
585  // HTMLs
586  QList<QgsLayoutItemHtml *> htmls;
587  c->layoutObjects<QgsLayoutItemHtml>( htmls );
588  for ( const auto &html : qgis::as_const( htmls ) )
589  {
590  if ( html->frameCount() == 0 )
591  continue;
592 
593  QgsLayoutFrame *htmlFrame = html->frame( 0 );
594  bool ok = false;
595  const QString htmlId = htmlFrame->id();
596  const QString url = mWmsParameters.layoutParameter( htmlId, ok );
597 
598  if ( !ok )
599  {
600  html->update();
601  continue;
602  }
603 
604  //remove exported Htmls referenced in the request
605  //but with empty string
606  if ( url.isEmpty() )
607  {
608  c->removeMultiFrame( html );
609  delete html;
610  continue;
611  }
612 
613  QUrl newUrl( url );
614  html->setUrl( newUrl );
615  html->update();
616  }
617 
618 
619  // legends
620  QList<QgsLayoutItemLegend *> legends;
621  c->layoutItems<QgsLayoutItemLegend>( legends );
622  for ( const auto &legend : qgis::as_const( legends ) )
623  {
624  if ( legend->autoUpdateModel() )
625  {
626  // the legend has an auto-update model
627  // we will update it with map's layers
628  const QgsLayoutItemMap *map = legend->linkedMap();
629  if ( !map )
630  {
631  continue;
632  }
633 
634  legend->setAutoUpdateModel( false );
635 
636  // get model and layer tree root of the legend
637  QgsLegendModel *model = legend->model();
638  QStringList layerSet;
639  const QList<QgsMapLayer *> layerList( map->layers() );
640  for ( const auto &layer : layerList )
641  layerSet << layer->id();
642 
643  //setLayerIdsToLegendModel( model, layerSet, map->scale() );
644 
645  // get model and layer tree root of the legend
646  QgsLayerTree *root = model->rootGroup();
647 
648  // get layerIds find in the layer tree root
649  const QStringList layerIds = root->findLayerIds();
650 
651  // find the layer in the layer tree
652  // remove it if the layer id is not in map layerIds
653  for ( const auto &layerId : layerIds )
654  {
655  QgsLayerTreeLayer *nodeLayer = root->findLayer( layerId );
656  if ( !nodeLayer )
657  {
658  continue;
659  }
660  if ( !layerSet.contains( layerId ) )
661  {
662  qobject_cast<QgsLayerTreeGroup *>( nodeLayer->parent() )->removeChildNode( nodeLayer );
663  }
664  else
665  {
666  QgsMapLayer *layer = nodeLayer->layer();
667  if ( !layer->isInScaleRange( map->scale() ) )
668  {
669  qobject_cast<QgsLayerTreeGroup *>( nodeLayer->parent() )->removeChildNode( nodeLayer );
670  }
671  }
672  }
674  }
675  }
676  return true;
677  }
678 
679  QImage *QgsRenderer::getMap( HitTest *hitTest )
680  {
681  QgsMapSettings mapSettings;
682  return getMap( mapSettings, hitTest );
683  }
684 
685  QImage *QgsRenderer::getMap( QgsMapSettings &mapSettings, HitTest *hitTest )
686  {
687  // check size
688  if ( !checkMaximumWidthHeight() )
689  {
690  throw QgsBadRequestException( QStringLiteral( "Size error" ),
691  QStringLiteral( "The requested map size is too large" ) );
692  }
693 
694  // get layers parameters
695  QList<QgsMapLayer *> layers;
696  QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
697 
698  // init layer restorer before doing anything
699  std::unique_ptr<QgsLayerRestorer> restorer;
700  restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
701 
702  // init stylized layers according to LAYERS/STYLES or SLD
703  QString sld = mWmsParameters.sldBody();
704  if ( !sld.isEmpty() )
705  {
706  layers = sldStylizedLayers( sld );
707  }
708  else
709  {
710  layers = stylizedLayers( params );
711  }
712 
713  // remove unwanted layers (restricted layers, ...)
714  removeUnwantedLayers( layers );
715 
716  // configure each layer with opacity, selection filter, ...
717  bool updateMapExtent = mWmsParameters.bbox().isEmpty();
718  for ( QgsMapLayer *layer : layers )
719  {
720  checkLayerReadPermissions( layer );
721 
722  for ( const QgsWmsParametersLayer &param : params )
723  {
724  if ( param.mNickname == layerNickname( *layer ) )
725  {
726  setLayerOpacity( layer, param.mOpacity );
727 
728  setLayerFilter( layer, param.mFilter );
729 
730  setLayerSelection( layer, param.mSelection );
731 
732  if ( updateMapExtent )
733  updateExtent( layer, mapSettings );
734 
735  break;
736  }
737  }
738 
739  setLayerAccessControlFilter( layer );
740  }
741 
742  // add highlight layers above others
743  layers = layers << highlightLayers( mWmsParameters.highlightLayersParameters() );
744 
745  // create the output image and the painter
746  std::unique_ptr<QPainter> painter;
747  std::unique_ptr<QImage> image( createImage() );
748 
749  // configure map settings (background, DPI, ...)
750  configureMapSettings( image.get(), mapSettings );
751 
752  // add layers to map settings (revert order for the rendering)
753  std::reverse( layers.begin(), layers.end() );
754  mapSettings.setLayers( layers );
755 
756  // rendering step for layers
757  painter.reset( layersRendering( mapSettings, *image, hitTest ) );
758 
759  // rendering step for annotations
760  annotationsRendering( painter.get() );
761 
762  // painting is terminated
763  painter->end();
764 
765  // scale output image if necessary (required by WMS spec)
766  QImage *scaledImage = scaleImage( image.get() );
767  if ( scaledImage )
768  image.reset( scaledImage );
769 
770  // return
771  return image.release();
772  }
773 
774  QgsDxfExport QgsRenderer::getDxf( const QMap<QString, QString> &options )
775  {
776  QgsDxfExport dxf;
777 
778  // set extent
779  QgsRectangle extent = mWmsParameters.bboxAsRectangle();
780  dxf.setExtent( extent );
781 
782  // get layers parameters
783  QList<QgsMapLayer *> layers;
784  QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
785 
786  // init layer restorer before doing anything
787  std::unique_ptr<QgsLayerRestorer> restorer;
788  restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
789 
790  // init stylized layers according to LAYERS/STYLES or SLD
791  QString sld = mWmsParameters.sldBody();
792  if ( !sld.isEmpty() )
793  {
794  layers = sldStylizedLayers( sld );
795  }
796  else
797  {
798  layers = stylizedLayers( params );
799  }
800 
801  // layer attributes options
802  QStringList layerAttributes;
803  QMap<QString, QString>::const_iterator layerAttributesIt = options.find( QStringLiteral( "LAYERATTRIBUTES" ) );
804  if ( layerAttributesIt != options.constEnd() )
805  {
806  layerAttributes = options.value( QStringLiteral( "LAYERATTRIBUTES" ) ).split( ',' );
807  }
808 
809  // only wfs layers are allowed to be published
810  QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *mProject );
811 
812  // get dxf layers
813  QList< QgsDxfExport::DxfLayer > dxfLayers;
814  int layerIdx = -1;
815  for ( QgsMapLayer *layer : layers )
816  {
817  layerIdx++;
818  if ( layer->type() != QgsMapLayer::VectorLayer )
819  continue;
820  if ( !wfsLayerIds.contains( layer->id() ) )
821  continue;
822 
823  checkLayerReadPermissions( layer );
824 
825  for ( const QgsWmsParametersLayer &param : params )
826  {
827  if ( param.mNickname == layerNickname( *layer ) )
828  {
829  setLayerOpacity( layer, param.mOpacity );
830 
831  setLayerFilter( layer, param.mFilter );
832 
833  break;
834  }
835  }
836 
837  setLayerAccessControlFilter( layer );
838 
839  // cast for dxf layers
840  QgsVectorLayer *vlayer = static_cast<QgsVectorLayer *>( layer );
841 
842  // get the layer attribute used in dxf
843  int layerAttribute = -1;
844  if ( layerAttributes.size() > layerIdx )
845  {
846  layerAttribute = vlayer->fields().indexFromName( layerAttributes.at( layerIdx ) );
847  }
848 
849  dxfLayers.append( QgsDxfExport::DxfLayer( vlayer, layerAttribute ) );
850  }
851 
852  // add layers to dxf
853  dxf.addLayers( dxfLayers );
854 
855  dxf.setLayerTitleAsName( options.contains( QStringLiteral( "USE_TITLE_AS_LAYERNAME" ) ) );
856 
857  //MODE
858  QMap<QString, QString>::const_iterator modeIt = options.find( QStringLiteral( "MODE" ) );
859 
861  if ( modeIt == options.constEnd() )
862  {
864  }
865  else
866  {
867  if ( modeIt->compare( QStringLiteral( "SymbolLayerSymbology" ), Qt::CaseInsensitive ) == 0 )
868  {
870  }
871  else if ( modeIt->compare( QStringLiteral( "FeatureSymbology" ), Qt::CaseInsensitive ) == 0 )
872  {
874  }
875  else
876  {
878  }
879  }
880  dxf.setSymbologyExport( se );
881 
882  //SCALE
883  QMap<QString, QString>::const_iterator scaleIt = options.find( QStringLiteral( "SCALE" ) );
884  if ( scaleIt != options.constEnd() )
885  {
886  dxf.setSymbologyScale( scaleIt->toDouble() );
887  }
888 
889  return dxf;
890  }
891 
892  static void infoPointToMapCoordinates( int i, int j, QgsPointXY *infoPoint, const QgsMapSettings &mapSettings )
893  {
894  //check if i, j are in the pixel range of the image
895  if ( i < 0 || i > mapSettings.outputSize().width() || j < 0 || j > mapSettings.outputSize().height() )
896  {
897  throw QgsBadRequestException( "InvalidPoint", "I/J parameters not within the pixel range" );
898  }
899 
900  double xRes = mapSettings.extent().width() / mapSettings.outputSize().width();
901  double yRes = mapSettings.extent().height() / mapSettings.outputSize().height();
902  infoPoint->setX( mapSettings.extent().xMinimum() + i * xRes + xRes / 2.0 );
903  infoPoint->setY( mapSettings.extent().yMaximum() - j * yRes - yRes / 2.0 );
904  }
905 
906  QByteArray QgsRenderer::getFeatureInfo( const QString &version )
907  {
908  // Verifying Mandatory parameters
909  // The QUERY_LAYERS parameter is Mandatory
910  QStringList queryLayers = flattenedQueryLayers();
911 
912  if ( queryLayers.isEmpty() )
913  {
914  QString msg = QObject::tr( "QUERY_LAYERS parameter is required for GetFeatureInfo" );
915  throw QgsBadRequestException( QStringLiteral( "LayerNotQueryable" ), msg );
916  }
917 
918  // The I/J parameters are Mandatory if they are not replaced by X/Y or FILTER or FILTER_GEOM
919  const bool ijDefined = !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty();
920  const bool xyDefined = !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty();
921  const bool filtersDefined = !mWmsParameters.filters().isEmpty();
922  const bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
923 
924  if ( !ijDefined && !xyDefined && !filtersDefined && !filterGeomDefined )
925  {
926  throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ),
927  QStringLiteral( "I/J parameters are required for GetFeatureInfo" ) );
928  }
929 
930  QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
931  if ( infoFormat == QgsWmsParameters::Format::NONE )
932  {
933  throw QgsBadRequestException( QStringLiteral( "InvalidFormat" ),
934  QStringLiteral( "Invalid INFO_FORMAT parameter" ) );
935  }
936 
937  // get layers parameters
938  QList<QgsMapLayer *> layers;
939  QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
940 
941  // init layer restorer before doing anything
942  std::unique_ptr<QgsLayerRestorer> restorer;
943  restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
944 
945  // init stylized layers according to LAYERS/STYLES or SLD
946  QString sld = mWmsParameters.sldBody();
947  if ( !sld.isEmpty() )
948  layers = sldStylizedLayers( sld );
949  else
950  layers = stylizedLayers( params );
951 
952  // add QUERY_LAYERS to list of available layers for more flexibility
953  for ( const QString &queryLayer : queryLayers )
954  {
955  if ( mNicknameLayers.contains( queryLayer )
956  && !layers.contains( mNicknameLayers[queryLayer] ) )
957  {
958  layers.append( mNicknameLayers[queryLayer] );
959  }
960  }
961 
962  // create the mapSettings and the output image
963  int imageWidth = mWmsParameters.widthAsInt();
964  int imageHeight = mWmsParameters.heightAsInt();
965 
966  // Provide default image width/height values if format is not image
967  if ( !( imageWidth && imageHeight ) && ! mWmsParameters.infoFormatIsImage() )
968  {
969  imageWidth = 10;
970  imageHeight = 10;
971  }
972 
973  QgsMapSettings mapSettings;
974  std::unique_ptr<QImage> outputImage( createImage( imageWidth, imageHeight ) );
975 
976  // The CRS parameter is considered as mandatory in configureMapSettings
977  // but in the case of filter parameter, CRS parameter has not to be mandatory
978  bool mandatoryCrsParam = true;
979  if ( filtersDefined && !ijDefined && !xyDefined && mWmsParameters.crs().isEmpty() )
980  {
981  mandatoryCrsParam = false;
982  }
983 
984  // configure map settings (background, DPI, ...)
985  configureMapSettings( outputImage.get(), mapSettings, mandatoryCrsParam );
986 
987  QgsMessageLog::logMessage( "mapSettings.destinationCrs(): " + mapSettings.destinationCrs().authid() );
988  QgsMessageLog::logMessage( "mapSettings.extent(): " + mapSettings.extent().toString() );
989  QgsMessageLog::logMessage( QStringLiteral( "mapSettings width = %1 height = %2" ).arg( mapSettings.outputSize().width() ).arg( mapSettings.outputSize().height() ) );
990  QgsMessageLog::logMessage( QStringLiteral( "mapSettings.mapUnitsPerPixel() = %1" ).arg( mapSettings.mapUnitsPerPixel() ) );
991 
992  QgsScaleCalculator scaleCalc( ( outputImage->logicalDpiX() + outputImage->logicalDpiY() ) / 2, mapSettings.destinationCrs().mapUnits() );
993  QgsRectangle mapExtent = mapSettings.extent();
994  double scaleDenominator = scaleCalc.calculate( mapExtent, outputImage->width() );
995 
996  // remove unwanted layers (restricted layers, ...)
997  removeUnwantedLayers( layers, scaleDenominator );
998  // remove non identifiable layers
999  //removeNonIdentifiableLayers( layers );
1000 
1001  for ( QgsMapLayer *layer : layers )
1002  {
1003  checkLayerReadPermissions( layer );
1004 
1005  for ( const QgsWmsParametersLayer &param : params )
1006  {
1007  if ( param.mNickname == layerNickname( *layer ) )
1008  {
1009  setLayerFilter( layer, param.mFilter );
1010 
1011  break;
1012  }
1013  }
1014 
1015  setLayerAccessControlFilter( layer );
1016  }
1017 
1018  // add layers to map settings (revert order for the rendering)
1019  std::reverse( layers.begin(), layers.end() );
1020  mapSettings.setLayers( layers );
1021 
1022  QDomDocument result = featureInfoDocument( layers, mapSettings, outputImage.get(), version );
1023 
1024  QByteArray ba;
1025 
1026  if ( infoFormat == QgsWmsParameters::Format::TEXT )
1027  ba = convertFeatureInfoToText( result );
1028  else if ( infoFormat == QgsWmsParameters::Format::HTML )
1029  ba = convertFeatureInfoToHtml( result );
1030  else
1031  ba = result.toByteArray();
1032 
1033  return ba;
1034  }
1035 
1036  QImage *QgsRenderer::createImage( int width, int height, bool useBbox ) const
1037  {
1038  if ( width < 0 )
1039  width = this->width();
1040 
1041  if ( height < 0 )
1042  height = this->height();
1043 
1044  //Adapt width / height if the aspect ratio does not correspond with the BBOX.
1045  //Required by WMS spec. 1.3.
1046  if ( useBbox && mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
1047  {
1048  QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
1049  if ( !mWmsParameters.bbox().isEmpty() && mapExtent.isEmpty() )
1050  {
1051  throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ),
1052  QStringLiteral( "Invalid BBOX parameter" ) );
1053  }
1054 
1055  QString crs = mWmsParameters.crs();
1056  if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
1057  {
1058  crs = QString( "EPSG:4326" );
1059  mapExtent.invert();
1060  }
1062  if ( outputCRS.hasAxisInverted() )
1063  {
1064  mapExtent.invert();
1065  }
1066  if ( !mapExtent.isEmpty() && height > 0 && width > 0 )
1067  {
1068  double mapWidthHeightRatio = mapExtent.width() / mapExtent.height();
1069  double imageWidthHeightRatio = static_cast<double>( width ) / static_cast<double>( height );
1070  if ( !qgsDoubleNear( mapWidthHeightRatio, imageWidthHeightRatio, 0.0001 ) )
1071  {
1072  // inspired by MapServer, mapdraw.c L115
1073  double cellsize = ( mapExtent.width() / static_cast<double>( width ) ) * 0.5 + ( mapExtent.height() / static_cast<double>( height ) ) * 0.5;
1074  width = mapExtent.width() / cellsize;
1075  height = mapExtent.height() / cellsize;
1076  }
1077  }
1078  }
1079 
1080  if ( width <= 0 || height <= 0 )
1081  {
1082  throw QgsException( QStringLiteral( "createImage: Invalid width / height parameters" ) );
1083  }
1084 
1085  std::unique_ptr<QImage> image;
1086 
1087  // use alpha channel only if necessary because it slows down performance
1088  QgsWmsParameters::Format format = mWmsParameters.format();
1089  bool transparent = mWmsParameters.transparentAsBool();
1090 
1091  if ( transparent && format != QgsWmsParameters::JPG )
1092  {
1093  image = qgis::make_unique<QImage>( width, height, QImage::Format_ARGB32_Premultiplied );
1094  image->fill( 0 );
1095  }
1096  else
1097  {
1098  image = qgis::make_unique<QImage>( width, height, QImage::Format_RGB32 );
1099  image->fill( mWmsParameters.backgroundColorAsColor() );
1100  }
1101 
1102  // Check that image was correctly created
1103  if ( image->isNull() )
1104  {
1105  throw QgsException( QStringLiteral( "createImage: image could not be created, check for out of memory conditions" ) );
1106  }
1107 
1108  //apply DPI parameter if present. This is an extension of Qgis Mapserver compared to WMS 1.3.
1109  //Because of backwards compatibility, this parameter is optional
1110  double OGC_PX_M = 0.00028; // OGC reference pixel size in meter, also used by qgis
1111  int dpm = 1 / OGC_PX_M;
1112  if ( !mWmsParameters.dpi().isEmpty() )
1113  dpm = mWmsParameters.dpiAsDouble() / 0.0254;
1114 
1115  image->setDotsPerMeterX( dpm );
1116  image->setDotsPerMeterY( dpm );
1117  return image.release();
1118  }
1119 
1120  void QgsRenderer::configureMapSettings( const QPaintDevice *paintDevice, QgsMapSettings &mapSettings, bool mandatoryCrsParam ) const
1121  {
1122  if ( !paintDevice )
1123  {
1124  throw QgsException( QStringLiteral( "configureMapSettings: no paint device" ) );
1125  }
1126 
1127  mapSettings.setOutputSize( QSize( paintDevice->width(), paintDevice->height() ) );
1128  mapSettings.setOutputDpi( paintDevice->logicalDpiX() );
1129 
1130  //map extent
1131  QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
1132  if ( !mWmsParameters.bbox().isEmpty() && mapExtent.isEmpty() )
1133  {
1134  throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ), QStringLiteral( "Invalid BBOX parameter" ) );
1135  }
1136 
1137  QString crs = mWmsParameters.crs();
1138  if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
1139  {
1140  crs = QString( "EPSG:4326" );
1141  mapExtent.invert();
1142  }
1143  else if ( crs.isEmpty() && !mandatoryCrsParam )
1144  {
1145  crs = QString( "EPSG:4326" );
1146  }
1147 
1148  QgsCoordinateReferenceSystem outputCRS;
1149 
1150  //wms spec says that CRS parameter is mandatory.
1152  if ( !outputCRS.isValid() )
1153  {
1154  QgsMessageLog::logMessage( QStringLiteral( "Error, could not create output CRS from EPSG" ) );
1155  throw QgsBadRequestException( QStringLiteral( "InvalidCRS" ), QStringLiteral( "Could not create output CRS" ) );
1156  }
1157 
1158  //then set destinationCrs
1159  mapSettings.setDestinationCrs( outputCRS );
1160 
1161  // Change x- and y- of BBOX for WMS 1.3.0 if axis inverted
1162  if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCRS.hasAxisInverted() )
1163  {
1164  mapExtent.invert();
1165  }
1166 
1167  mapSettings.setExtent( mapExtent );
1168 
1169  /* Define the background color
1170  * Transparent or colored
1171  */
1172  QgsWmsParameters::Format format = mWmsParameters.format();
1173  bool transparent = mWmsParameters.transparentAsBool();
1174  QColor backgroundColor = mWmsParameters.backgroundColorAsColor();
1175 
1176  //set background color
1177  if ( transparent && format != QgsWmsParameters::JPG )
1178  {
1179  mapSettings.setBackgroundColor( QColor( 0, 0, 0, 0 ) );
1180  }
1181  else if ( backgroundColor.isValid() )
1182  {
1183  mapSettings.setBackgroundColor( backgroundColor );
1184  }
1185 
1186  // add context from project (global variables, ...)
1187  QgsExpressionContext context = mProject->createExpressionContext();
1188  context << QgsExpressionContextUtils::mapSettingsScope( mapSettings );
1189  mapSettings.setExpressionContext( context );
1190 
1191  // add labeling engine settings
1192  mapSettings.setLabelingEngineSettings( mProject->labelingEngineSettings() );
1193 
1194  // enable rendering optimization
1196 
1197  // set selection color
1198  int myRed = mProject->readNumEntry( "Gui", "/SelectionColorRedPart", 255 );
1199  int myGreen = mProject->readNumEntry( "Gui", "/SelectionColorGreenPart", 255 );
1200  int myBlue = mProject->readNumEntry( "Gui", "/SelectionColorBluePart", 0 );
1201  int myAlpha = mProject->readNumEntry( "Gui", "/SelectionColorAlphaPart", 255 );
1202  mapSettings.setSelectionColor( QColor( myRed, myGreen, myBlue, myAlpha ) );
1203  }
1204 
1205  QDomDocument QgsRenderer::featureInfoDocument( QList<QgsMapLayer *> &layers, const QgsMapSettings &mapSettings,
1206  const QImage *outputImage, const QString &version ) const
1207  {
1208 
1209  const QStringList queryLayers = flattenedQueryLayers( );
1210 
1211  bool ijDefined = ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() );
1212 
1213  bool xyDefined = ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() );
1214 
1215  bool filtersDefined = !mWmsParameters.filters().isEmpty();
1216 
1217  bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
1218 
1219  int featureCount = mWmsParameters.featureCountAsInt();
1220  if ( featureCount < 1 )
1221  {
1222  featureCount = 1;
1223  }
1224 
1225  int i = mWmsParameters.iAsInt();
1226  int j = mWmsParameters.jAsInt();
1227  if ( xyDefined && !ijDefined )
1228  {
1229  i = mWmsParameters.xAsInt();
1230  j = mWmsParameters.yAsInt();
1231  }
1232  int width = mWmsParameters.widthAsInt();
1233  int height = mWmsParameters.heightAsInt();
1234  if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) )
1235  {
1236  i *= ( outputImage->width() / static_cast<double>( width ) );
1237  j *= ( outputImage->height() / static_cast<double>( height ) );
1238  }
1239 
1240  // init search variables
1241  std::unique_ptr<QgsRectangle> featuresRect;
1242  std::unique_ptr<QgsGeometry> filterGeom;
1243  std::unique_ptr<QgsPointXY> infoPoint;
1244 
1245  if ( i != -1 && j != -1 )
1246  {
1247  infoPoint.reset( new QgsPointXY() );
1248  infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings );
1249  }
1250  else if ( filtersDefined )
1251  {
1252  featuresRect.reset( new QgsRectangle() );
1253  }
1254  else if ( filterGeomDefined )
1255  {
1256  filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) ) );
1257  }
1258 
1259  QDomDocument result;
1260 
1261  QDomElement getFeatureInfoElement;
1262  QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
1263  if ( infoFormat == QgsWmsParameters::Format::GML )
1264  {
1265  getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) );
1266  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) );
1267  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
1268  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
1269  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ows" ), QStringLiteral( "http://www.opengis.net/ows" ) );
1270  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1271  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://qgis.org/gml" ) );
1272  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
1273  getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" ) );
1274  }
1275  else
1276  {
1277  QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject );
1278  if ( featureInfoElemName.isEmpty() )
1279  {
1280  featureInfoElemName = QStringLiteral( "GetFeatureInfoResponse" );
1281  }
1282  QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject );
1283  if ( featureInfoElemNs.isEmpty() )
1284  {
1285  getFeatureInfoElement = result.createElement( featureInfoElemName );
1286  }
1287  else
1288  {
1289  getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName );
1290  }
1291  //feature info schema
1292  QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject );
1293  if ( !featureInfoSchema.isEmpty() )
1294  {
1295  getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
1296  getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), featureInfoSchema );
1297  }
1298  }
1299  result.appendChild( getFeatureInfoElement );
1300 
1301  //Render context is needed to determine feature visibility for vector layers
1302  QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings );
1303 
1304  bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject );
1305 
1306  //layers can have assigned a different name for GetCapabilities
1307  QHash<QString, QString> layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject );
1308 
1309  for ( const QString &queryLayer : queryLayers )
1310  {
1311  bool validLayer = false;
1312  bool queryableLayer = true;
1313  for ( QgsMapLayer *layer : qgis::as_const( layers ) )
1314  {
1315  if ( queryLayer == layerNickname( *layer ) )
1316  {
1317  validLayer = true;
1318  queryableLayer = layer->flags().testFlag( QgsMapLayer::Identifiable );
1319  if ( !queryableLayer )
1320  {
1321  break;
1322  }
1323 
1324  QDomElement layerElement;
1325  if ( infoFormat == QgsWmsParameters::Format::GML )
1326  {
1327  layerElement = getFeatureInfoElement;
1328  }
1329  else
1330  {
1331  layerElement = result.createElement( QStringLiteral( "Layer" ) );
1332  QString layerName = queryLayer;
1333 
1334  //check if the layer is given a different name for GetFeatureInfo output
1335  QHash<QString, QString>::const_iterator layerAliasIt = layerAliasMap.find( layerName );
1336  if ( layerAliasIt != layerAliasMap.constEnd() )
1337  {
1338  layerName = layerAliasIt.value();
1339  }
1340 
1341  layerElement.setAttribute( QStringLiteral( "name" ), layerName );
1342  getFeatureInfoElement.appendChild( layerElement );
1343  if ( sia2045 ) //the name might not be unique after alias replacement
1344  {
1345  layerElement.setAttribute( QStringLiteral( "id" ), layer->id() );
1346  }
1347  }
1348 
1349  if ( layer->type() == QgsMapLayer::VectorLayer )
1350  {
1351  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
1352  if ( vectorLayer )
1353  {
1354  ( void )featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, featuresRect.get(), filterGeom.get() );
1355  break;
1356  }
1357  }
1358  else
1359  {
1360  QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
1361  if ( !rasterLayer )
1362  {
1363  break;
1364  }
1365  if ( !infoPoint )
1366  {
1367  break;
1368  }
1369  QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) );
1370  if ( !rasterLayer->extent().contains( layerInfoPoint ) )
1371  {
1372  break;
1373  }
1374  if ( infoFormat == QgsWmsParameters::Format::GML )
1375  {
1376  layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1377  getFeatureInfoElement.appendChild( layerElement );
1378  }
1379 
1380  ( void )featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version );
1381  }
1382  break;
1383  }
1384  }
1385  if ( !validLayer && !mNicknameLayers.contains( queryLayer ) && !mLayerGroups.contains( queryLayer ) )
1386  {
1387  QString msg = QObject::tr( "Layer '%1' not found" ).arg( queryLayer );
1388  throw QgsBadRequestException( QStringLiteral( "LayerNotDefined" ), msg );
1389  }
1390  else if ( ( validLayer && !queryableLayer ) || ( !validLayer && mLayerGroups.contains( queryLayer ) ) )
1391  {
1392  auto queryLayerName { queryLayer };
1393  // Check if this layer belongs to a group and the group has any queryable layers
1394  bool hasGroupAndQueryable { false };
1395  if ( ! mWmsParameters.queryLayersNickname().contains( queryLayer ) )
1396  {
1397  // Find which group this layer belongs to
1398  const auto &constNicks { mWmsParameters.queryLayersNickname() };
1399  for ( const auto &ql : constNicks )
1400  {
1401  if ( mLayerGroups.contains( ql ) )
1402  {
1403  const auto &constLayers { mLayerGroups[ql] };
1404  for ( const auto &ml : constLayers )
1405  {
1406  if ( ( ! ml->shortName().isEmpty() && ml->shortName() == queryLayer ) || ( ml->name() == queryLayer ) )
1407  {
1408  queryLayerName = ql;
1409  }
1410  if ( ml->flags().testFlag( QgsMapLayer::Identifiable ) )
1411  {
1412  hasGroupAndQueryable = true;
1413  break;
1414  }
1415  }
1416  break;
1417  }
1418  }
1419  }
1420  // Only throw if it's not a group or the group has no queryable children
1421  if ( ! hasGroupAndQueryable )
1422  {
1423  const QString msg { QObject::tr( "The layer '%1' is not queryable." ).arg( queryLayerName ) };
1424  throw QgsBadRequestException( QStringLiteral( "LayerNotQueryable" ), msg );
1425  }
1426  }
1427  }
1428 
1429  if ( featuresRect )
1430  {
1431  if ( infoFormat == QgsWmsParameters::Format::GML )
1432  {
1433  QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) );
1434  QDomElement boxElem;
1435  int gmlVersion = mWmsParameters.infoFormatVersion();
1436  if ( gmlVersion < 3 )
1437  {
1438  boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 );
1439  }
1440  else
1441  {
1442  boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 );
1443  }
1444 
1446  if ( crs.isValid() )
1447  {
1448  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1449  }
1450  bBoxElem.appendChild( boxElem );
1451  getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
1452  }
1453  else
1454  {
1455  QDomElement bBoxElem = result.createElement( QStringLiteral( "BoundingBox" ) );
1456  bBoxElem.setAttribute( QStringLiteral( "CRS" ), mapSettings.destinationCrs().authid() );
1457  bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( featuresRect->xMinimum(), 8 ) );
1458  bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( featuresRect->xMaximum(), 8 ) );
1459  bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( featuresRect->yMinimum(), 8 ) );
1460  bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( featuresRect->yMaximum(), 8 ) );
1461  getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
1462  }
1463  }
1464 
1465  if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML )
1466  {
1467  convertFeatureInfoToSia2045( result );
1468  }
1469 
1470  return result;
1471  }
1472 
1473  bool QgsRenderer::featureInfoFromVectorLayer( QgsVectorLayer *layer,
1474  const QgsPointXY *infoPoint,
1475  int nFeatures,
1476  QDomDocument &infoDocument,
1477  QDomElement &layerElement,
1478  const QgsMapSettings &mapSettings,
1479  QgsRenderContext &renderContext,
1480  const QString &version,
1481  QgsRectangle *featureBBox,
1482  QgsGeometry *filterGeom ) const
1483  {
1484  if ( !layer )
1485  {
1486  return false;
1487  }
1488 
1489  QgsFeatureRequest fReq;
1490 
1491  // Transform filter geometry to layer CRS
1492  std::unique_ptr<QgsGeometry> layerFilterGeom;
1493  if ( filterGeom )
1494  {
1495  layerFilterGeom.reset( new QgsGeometry( *filterGeom ) );
1496  layerFilterGeom->transform( QgsCoordinateTransform( mapSettings.destinationCrs(), layer->crs(), fReq.transformContext() ) );
1497  }
1498 
1499  //we need a selection rect (0.01 of map width)
1500  QgsRectangle mapRect = mapSettings.extent();
1501  QgsRectangle layerRect = mapSettings.mapToLayerCoordinates( layer, mapRect );
1502 
1503 
1504  QgsRectangle searchRect;
1505 
1506  //info point could be 0 in case there is only an attribute filter
1507  if ( infoPoint )
1508  {
1509  searchRect = featureInfoSearchRect( layer, mapSettings, renderContext, *infoPoint );
1510  }
1511  else if ( layerFilterGeom )
1512  {
1513  searchRect = layerFilterGeom->boundingBox();
1514  }
1515  else if ( !mWmsParameters.bbox().isEmpty() )
1516  {
1517  searchRect = layerRect;
1518  }
1519 
1520  //do a select with searchRect and go through all the features
1521 
1522  QgsFeature feature;
1523  QgsAttributes featureAttributes;
1524  int featureCounter = 0;
1525  layer->updateFields();
1526  const QgsFields fields = layer->fields();
1527  bool addWktGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );
1528  bool segmentizeWktGeometry = QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( *mProject );
1529  const QSet<QString> &excludedAttributes = layer->excludeAttributesWms();
1530 
1531  bool hasGeometry = addWktGeometry || featureBBox || layerFilterGeom;
1533 
1534  if ( ! searchRect.isEmpty() )
1535  {
1536  fReq.setFilterRect( searchRect );
1537  }
1538  else
1539  {
1540  fReq.setFlags( fReq.flags() & ~ QgsFeatureRequest::ExactIntersect );
1541  }
1542 
1543 
1544  if ( layerFilterGeom )
1545  {
1546  fReq.setFilterExpression( QString( "intersects( $geometry, geom_from_wkt('%1') )" ).arg( layerFilterGeom->asWkt() ) );
1547  }
1548 
1549 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1550  mAccessControl->filterFeatures( layer, fReq );
1551 
1552  QStringList attributes;
1553  for ( const QgsField &field : fields )
1554  {
1555  attributes.append( field.name() );
1556  }
1557  attributes = mAccessControl->layerAttributes( layer, attributes );
1558  fReq.setSubsetOfAttributes( attributes, layer->fields() );
1559 #endif
1560 
1561  QgsFeatureIterator fit = layer->getFeatures( fReq );
1562  std::unique_ptr< QgsFeatureRenderer > r2( layer->renderer() ? layer->renderer()->clone() : nullptr );
1563  if ( r2 )
1564  {
1565  r2->startRender( renderContext, layer->fields() );
1566  }
1567 
1568  bool featureBBoxInitialized = false;
1569  while ( fit.nextFeature( feature ) )
1570  {
1571  if ( layer->wkbType() == QgsWkbTypes::NoGeometry && ! searchRect.isEmpty() )
1572  {
1573  break;
1574  }
1575 
1576  ++featureCounter;
1577  if ( featureCounter > nFeatures )
1578  {
1579  break;
1580  }
1581 
1582  renderContext.expressionContext().setFeature( feature );
1583 
1584  if ( layer->wkbType() != QgsWkbTypes::NoGeometry && ! searchRect.isEmpty() )
1585  {
1586  if ( !r2 )
1587  {
1588  continue;
1589  }
1590 
1591  //check if feature is rendered at all
1592  bool render = r2->willRenderFeature( feature, renderContext );
1593  if ( !render )
1594  {
1595  continue;
1596  }
1597  }
1598 
1599  QgsRectangle box;
1600  if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry )
1601  {
1602  box = mapSettings.layerExtentToOutputExtent( layer, feature.geometry().boundingBox() );
1603  if ( featureBBox ) //extend feature info bounding box if requested
1604  {
1605  if ( !featureBBoxInitialized && featureBBox->isEmpty() )
1606  {
1607  *featureBBox = box;
1608  featureBBoxInitialized = true;
1609  }
1610  else
1611  {
1612  featureBBox->combineExtentWith( box );
1613  }
1614  }
1615  }
1616 
1618  if ( layer->crs() != mapSettings.destinationCrs() )
1619  {
1620  outputCrs = mapSettings.destinationCrs();
1621  }
1622 
1623  if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
1624  {
1625  bool withGeom = layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry;
1626  int gmlVersion = mWmsParameters.infoFormatVersion();
1627  QString typeName = layerNickname( *layer );
1628  QDomElement elem = createFeatureGML(
1629  &feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, gmlVersion
1630 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1631  , &attributes
1632 #endif
1633  );
1634  QDomElement featureMemberElem = infoDocument.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1635  featureMemberElem.appendChild( elem );
1636  layerElement.appendChild( featureMemberElem );
1637  continue;
1638  }
1639  else
1640  {
1641  QDomElement featureElement = infoDocument.createElement( QStringLiteral( "Feature" ) );
1642  featureElement.setAttribute( QStringLiteral( "id" ), QgsServerFeatureId::getServerFid( feature, layer->dataProvider()->pkAttributeIndexes() ) );
1643  layerElement.appendChild( featureElement );
1644 
1645  //read all attribute values from the feature
1646  featureAttributes = feature.attributes();
1647  for ( int i = 0; i < featureAttributes.count(); ++i )
1648  {
1649  //skip attribute if it is explicitly excluded from WMS publication
1650  if ( excludedAttributes.contains( fields.at( i ).name() ) )
1651  {
1652  continue;
1653  }
1654 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1655  //skip attribute if it is excluded by access control
1656  if ( !attributes.contains( fields.at( i ).name() ) )
1657  {
1658  continue;
1659  }
1660 #endif
1661 
1662  //replace attribute name if there is an attribute alias?
1663  QString attributeName = layer->attributeDisplayName( i );
1664 
1665  QDomElement attributeElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1666  attributeElement.setAttribute( QStringLiteral( "name" ), attributeName );
1667  const QgsEditorWidgetSetup setup = layer->editorWidgetSetup( i );
1668  attributeElement.setAttribute( QStringLiteral( "value" ),
1670  replaceValueMapAndRelation(
1671  layer, i,
1672  featureAttributes[i] ),
1673  &renderContext.expressionContext() )
1674  );
1675  featureElement.appendChild( attributeElement );
1676  }
1677 
1678  //add maptip attribute based on html/expression (in case there is no maptip attribute)
1679  QString mapTip = layer->mapTipTemplate();
1680  if ( !mapTip.isEmpty() && mWmsParameters.withMapTip() )
1681  {
1682  QDomElement maptipElem = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1683  maptipElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maptip" ) );
1684  maptipElem.setAttribute( QStringLiteral( "value" ), QgsExpression::replaceExpressionText( mapTip, &renderContext.expressionContext() ) );
1685  featureElement.appendChild( maptipElem );
1686  }
1687 
1688  //append feature bounding box to feature info xml
1689  if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry )
1690  {
1691  QDomElement bBoxElem = infoDocument.createElement( QStringLiteral( "BoundingBox" ) );
1692  bBoxElem.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", outputCrs.authid() );
1693  bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( box.xMinimum(), getWMSPrecision() ) );
1694  bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( box.xMaximum(), getWMSPrecision() ) );
1695  bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( box.yMinimum(), getWMSPrecision() ) );
1696  bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( box.yMaximum(), getWMSPrecision() ) );
1697  featureElement.appendChild( bBoxElem );
1698  }
1699 
1700  //also append the wkt geometry as an attribute
1701  if ( layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry && hasGeometry )
1702  {
1703  QgsGeometry geom = feature.geometry();
1704  if ( !geom.isNull() )
1705  {
1706  if ( layer->crs() != outputCrs )
1707  {
1708  QgsCoordinateTransform transform = mapSettings.layerTransform( layer );
1709  if ( transform.isValid() )
1710  geom.transform( transform );
1711  }
1712 
1713  if ( segmentizeWktGeometry )
1714  {
1715  const QgsAbstractGeometry *abstractGeom = geom.constGet();
1716  if ( abstractGeom )
1717  {
1718  if ( QgsWkbTypes::isCurvedType( abstractGeom->wkbType() ) )
1719  {
1720  QgsAbstractGeometry *segmentizedGeom = abstractGeom->segmentize();
1721  geom.set( segmentizedGeom );
1722  }
1723  }
1724  }
1725  QDomElement geometryElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1726  geometryElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "geometry" ) );
1727  geometryElement.setAttribute( QStringLiteral( "value" ), geom.asWkt( getWMSPrecision() ) );
1728  geometryElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "derived" ) );
1729  featureElement.appendChild( geometryElement );
1730  }
1731  }
1732  }
1733  }
1734  if ( r2 )
1735  {
1736  r2->stopRender( renderContext );
1737  }
1738 
1739  return true;
1740  }
1741 
1742  bool QgsRenderer::featureInfoFromRasterLayer( QgsRasterLayer *layer,
1743  const QgsMapSettings &mapSettings,
1744  const QgsPointXY *infoPoint,
1745  QDomDocument &infoDocument,
1746  QDomElement &layerElement,
1747  const QString &version ) const
1748  {
1749  Q_UNUSED( version );
1750 
1751  if ( !infoPoint || !layer || !layer->dataProvider() )
1752  {
1753  return false;
1754  }
1755 
1756  QgsMessageLog::logMessage( QStringLiteral( "infoPoint: %1 %2" ).arg( infoPoint->x() ).arg( infoPoint->y() ) );
1757 
1760  {
1761  return false;
1762  }
1763 
1764  const QgsRaster::IdentifyFormat identifyFormat { static_cast<bool>( layer->dataProvider()->capabilities() &
1766  QgsRaster::IdentifyFormat::IdentifyFormatFeature :
1767  QgsRaster::IdentifyFormat::IdentifyFormatValue };
1768 
1769  QgsRasterIdentifyResult identifyResult;
1770  if ( layer->crs() != mapSettings.destinationCrs() )
1771  {
1772  const QgsRectangle extent { mapSettings.extent() };
1773  const QgsCoordinateTransform transform { mapSettings.destinationCrs(), layer->crs(), mapSettings.transformContext() };
1774  if ( ! transform.isValid() )
1775  {
1776  throw QgsBadRequestException( QStringLiteral( "InvalidCRS" ), QStringLiteral( "CRS transform error from %1 to %2 in layer %3" )
1777  .arg( mapSettings.destinationCrs().authid() )
1778  .arg( layer->crs().authid() )
1779  .arg( layer->name() ) );
1780  }
1781  identifyResult = layer->dataProvider()->identify( *infoPoint, identifyFormat, transform.transform( extent ), mapSettings.outputSize().width(), mapSettings.outputSize().height() );
1782  }
1783  else
1784  {
1785  identifyResult = layer->dataProvider()->identify( *infoPoint, identifyFormat, mapSettings.extent(), mapSettings.outputSize().width(), mapSettings.outputSize().height() );
1786  }
1787 
1788  if ( !identifyResult.isValid() )
1789  return false;
1790 
1791  QMap<int, QVariant> attributes = identifyResult.results();
1792 
1793  if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
1794  {
1795  QgsFeature feature;
1796  QgsFields fields;
1797  QgsCoordinateReferenceSystem layerCrs = layer->crs();
1798  int gmlVersion = mWmsParameters.infoFormatVersion();
1799  QString typeName = layerNickname( *layer );
1800 
1801  if ( identifyFormat == QgsRaster::IdentifyFormatValue )
1802  {
1803  feature.initAttributes( attributes.count() );
1804  int index = 0;
1805  for ( auto it = attributes.constBegin(); it != attributes.constEnd(); ++it )
1806  {
1807  fields.append( QgsField( layer->bandName( it.key() ), QVariant::Double ) );
1808  feature.setAttribute( index++, QString::number( it.value().toDouble() ) );
1809  }
1810  feature.setFields( fields );
1811  QDomElement elem = createFeatureGML(
1812  &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
1813  layerElement.appendChild( elem );
1814  }
1815  else
1816  {
1817  const auto values = identifyResult.results();
1818  for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
1819  {
1820  QVariant value = it.value();
1821  if ( value.type() == QVariant::Bool && !value.toBool() )
1822  {
1823  // sublayer not visible or not queryable
1824  continue;
1825  }
1826 
1827  if ( value.type() == QVariant::String )
1828  {
1829  continue;
1830  }
1831 
1832  // list of feature stores for a single sublayer
1833  const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>();
1834 
1835  for ( const QgsFeatureStore &featureStore : featureStoreList )
1836  {
1837  const QgsFeatureList storeFeatures = featureStore.features();
1838  for ( const QgsFeature &feature : storeFeatures )
1839  {
1840  QDomElement elem = createFeatureGML(
1841  &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
1842  layerElement.appendChild( elem );
1843  }
1844  }
1845  }
1846  }
1847  }
1848  else
1849  {
1850  if ( identifyFormat == QgsRaster::IdentifyFormatValue )
1851  {
1852  for ( auto it = attributes.constBegin(); it != attributes.constEnd(); ++it )
1853  {
1854  QDomElement attributeElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1855  attributeElement.setAttribute( QStringLiteral( "name" ), layer->bandName( it.key() ) );
1856  attributeElement.setAttribute( QStringLiteral( "value" ), QString::number( it.value().toDouble() ) );
1857  layerElement.appendChild( attributeElement );
1858  }
1859  }
1860  else // feature
1861  {
1862  const auto values = identifyResult.results();
1863  for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
1864  {
1865  QVariant value = it.value();
1866  if ( value.type() == QVariant::Bool && !value.toBool() )
1867  {
1868  // sublayer not visible or not queryable
1869  continue;
1870  }
1871 
1872  if ( value.type() == QVariant::String )
1873  {
1874  continue;
1875  }
1876 
1877  // list of feature stores for a single sublayer
1878  const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>();
1879  for ( const QgsFeatureStore &featureStore : featureStoreList )
1880  {
1881  const QgsFeatureList storeFeatures = featureStore.features();
1882  for ( const QgsFeature &feature : storeFeatures )
1883  {
1884  for ( const auto &fld : feature.fields() )
1885  {
1886  const auto val { feature.attribute( fld.name() )};
1887  if ( val.isValid() )
1888  {
1889  QDomElement attributeElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1890  attributeElement.setAttribute( QStringLiteral( "name" ), fld.name() );
1891  attributeElement.setAttribute( QStringLiteral( "value" ), val.toString() );
1892  layerElement.appendChild( attributeElement );
1893  }
1894  }
1895  }
1896  }
1897  }
1898  }
1899  }
1900  return true;
1901  }
1902 
1903  bool QgsRenderer::testFilterStringSafety( const QString &filter ) const
1904  {
1905  //; too dangerous for sql injections
1906  if ( filter.contains( QLatin1String( ";" ) ) )
1907  {
1908  return false;
1909  }
1910 
1911  QStringList tokens = filter.split( ' ', QString::SkipEmptyParts );
1912  groupStringList( tokens, QStringLiteral( "'" ) );
1913  groupStringList( tokens, QStringLiteral( "\"" ) );
1914 
1915  for ( auto tokenIt = tokens.constBegin() ; tokenIt != tokens.constEnd(); ++tokenIt )
1916  {
1917  //whitelist of allowed characters and keywords
1918  if ( tokenIt->compare( QLatin1String( "," ) ) == 0
1919  || tokenIt->compare( QLatin1String( "(" ) ) == 0
1920  || tokenIt->compare( QLatin1String( ")" ) ) == 0
1921  || tokenIt->compare( QLatin1String( "=" ) ) == 0
1922  || tokenIt->compare( QLatin1String( "!=" ) ) == 0
1923  || tokenIt->compare( QLatin1String( "<" ) ) == 0
1924  || tokenIt->compare( QLatin1String( "<=" ) ) == 0
1925  || tokenIt->compare( QLatin1String( ">" ) ) == 0
1926  || tokenIt->compare( QLatin1String( ">=" ) ) == 0
1927  || tokenIt->compare( QLatin1String( "%" ) ) == 0
1928  || tokenIt->compare( QLatin1String( "IS" ), Qt::CaseInsensitive ) == 0
1929  || tokenIt->compare( QLatin1String( "NOT" ), Qt::CaseInsensitive ) == 0
1930  || tokenIt->compare( QLatin1String( "NULL" ), Qt::CaseInsensitive ) == 0
1931  || tokenIt->compare( QLatin1String( "AND" ), Qt::CaseInsensitive ) == 0
1932  || tokenIt->compare( QLatin1String( "OR" ), Qt::CaseInsensitive ) == 0
1933  || tokenIt->compare( QLatin1String( "IN" ), Qt::CaseInsensitive ) == 0
1934  || tokenIt->compare( QLatin1String( "LIKE" ), Qt::CaseInsensitive ) == 0
1935  || tokenIt->compare( QLatin1String( "ILIKE" ), Qt::CaseInsensitive ) == 0
1936  || tokenIt->compare( QLatin1String( "DMETAPHONE" ), Qt::CaseInsensitive ) == 0
1937  || tokenIt->compare( QLatin1String( "SOUNDEX" ), Qt::CaseInsensitive ) == 0 )
1938  {
1939  continue;
1940  }
1941 
1942  //numbers are OK
1943  bool isNumeric;
1944  tokenIt->toDouble( &isNumeric );
1945  if ( isNumeric )
1946  {
1947  continue;
1948  }
1949 
1950  //numeric strings need to be quoted once either with single or with double quotes
1951 
1952  //empty strings are OK
1953  if ( *tokenIt == QLatin1String( "''" ) )
1954  {
1955  continue;
1956  }
1957 
1958  //single quote
1959  if ( tokenIt->size() > 2
1960  && ( *tokenIt )[0] == QChar( '\'' )
1961  && ( *tokenIt )[tokenIt->size() - 1] == QChar( '\'' )
1962  && ( *tokenIt )[1] != QChar( '\'' )
1963  && ( *tokenIt )[tokenIt->size() - 2] != QChar( '\'' ) )
1964  {
1965  continue;
1966  }
1967 
1968  //double quote
1969  if ( tokenIt->size() > 2
1970  && ( *tokenIt )[0] == QChar( '"' )
1971  && ( *tokenIt )[tokenIt->size() - 1] == QChar( '"' )
1972  && ( *tokenIt )[1] != QChar( '"' )
1973  && ( *tokenIt )[tokenIt->size() - 2] != QChar( '"' ) )
1974  {
1975  continue;
1976  }
1977 
1978  return false;
1979  }
1980 
1981  return true;
1982  }
1983 
1984  void QgsRenderer::groupStringList( QStringList &list, const QString &groupString )
1985  {
1986  //group contents within single quotes together
1987  bool groupActive = false;
1988  int startGroup = -1;
1989  QString concatString;
1990 
1991  for ( int i = 0; i < list.size(); ++i )
1992  {
1993  QString &str = list[i];
1994  if ( str.startsWith( groupString ) )
1995  {
1996  startGroup = i;
1997  groupActive = true;
1998  concatString.clear();
1999  }
2000 
2001  if ( groupActive )
2002  {
2003  if ( i != startGroup )
2004  {
2005  concatString.append( " " );
2006  }
2007  concatString.append( str );
2008  }
2009 
2010  if ( str.endsWith( groupString ) )
2011  {
2012  int endGroup = i;
2013  groupActive = false;
2014 
2015  if ( startGroup != -1 )
2016  {
2017  list[startGroup] = concatString;
2018  for ( int j = startGroup + 1; j <= endGroup; ++j )
2019  {
2020  list.removeAt( startGroup + 1 );
2021  --i;
2022  }
2023  }
2024 
2025  concatString.clear();
2026  startGroup = -1;
2027  }
2028  }
2029  }
2030 
2031  bool QgsRenderer::checkMaximumWidthHeight() const
2032  {
2033  //test if maxWidth / maxHeight set and WIDTH / HEIGHT parameter is in the range
2035  int width = this->width();
2036  if ( wmsMaxWidth != -1 && width > wmsMaxWidth )
2037  {
2038  return false;
2039  }
2040 
2042  int height = this->height();
2043  if ( wmsMaxHeight != -1 && height > wmsMaxHeight )
2044  {
2045  return false;
2046  }
2047 
2048  // Sanity check from internal QImage checks (see qimage.cpp)
2049  // this is to report a meaningful error message in case of
2050  // image creation failure and to differentiate it from out
2051  // of memory conditions.
2052 
2053  // depth for now it cannot be anything other than 32, but I don't like
2054  // to hardcode it: I hope we will support other depths in the future.
2055  uint depth = 32;
2056  switch ( mWmsParameters.format() )
2057  {
2058  case QgsWmsParameters::Format::JPG:
2060  default:
2061  depth = 32;
2062  }
2063 
2064  const int bytes_per_line = ( ( width * depth + 31 ) >> 5 ) << 2; // bytes per scanline (must be multiple of 4)
2065 
2066  if ( std::numeric_limits<int>::max() / depth < static_cast<uint>( width )
2067  || bytes_per_line <= 0
2068  || height <= 0
2069  || std::numeric_limits<int>::max() / static_cast<uint>( bytes_per_line ) < static_cast<uint>( height )
2070  || std::numeric_limits<int>::max() / sizeof( uchar * ) < static_cast<uint>( height ) )
2071  return false;
2072 
2073  return true;
2074  }
2075 
2076  void QgsRenderer::convertFeatureInfoToSia2045( QDomDocument &doc ) const
2077  {
2078  QDomDocument SIAInfoDoc;
2079  QDomElement infoDocElement = doc.documentElement();
2080  QDomElement SIAInfoDocElement = SIAInfoDoc.importNode( infoDocElement, false ).toElement();
2081  SIAInfoDoc.appendChild( SIAInfoDocElement );
2082 
2083  QString currentAttributeName;
2084  QString currentAttributeValue;
2085  QDomElement currentAttributeElem;
2086  QString currentLayerName;
2087  QDomElement currentLayerElem;
2088  QDomNodeList layerNodeList = infoDocElement.elementsByTagName( QStringLiteral( "Layer" ) );
2089  for ( int i = 0; i < layerNodeList.size(); ++i )
2090  {
2091  currentLayerElem = layerNodeList.at( i ).toElement();
2092  currentLayerName = currentLayerElem.attribute( QStringLiteral( "name" ) );
2093 
2094  QDomElement currentFeatureElem;
2095 
2096  QDomNodeList featureList = currentLayerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2097  if ( featureList.isEmpty() )
2098  {
2099  //raster?
2100  QDomNodeList attributeList = currentLayerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2101  QDomElement rasterLayerElem;
2102  if ( !attributeList.isEmpty() )
2103  {
2104  rasterLayerElem = SIAInfoDoc.createElement( currentLayerName );
2105  }
2106  for ( int j = 0; j < attributeList.size(); ++j )
2107  {
2108  currentAttributeElem = attributeList.at( j ).toElement();
2109  currentAttributeName = currentAttributeElem.attribute( QStringLiteral( "name" ) );
2110  currentAttributeValue = currentAttributeElem.attribute( QStringLiteral( "value" ) );
2111  QDomElement outAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
2112  QDomText outAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
2113  outAttributeElem.appendChild( outAttributeText );
2114  rasterLayerElem.appendChild( outAttributeElem );
2115  }
2116  if ( !attributeList.isEmpty() )
2117  {
2118  SIAInfoDocElement.appendChild( rasterLayerElem );
2119  }
2120  }
2121  else //vector
2122  {
2123  //property attributes
2124  QSet<QString> layerPropertyAttributes;
2125  QString currentLayerId = currentLayerElem.attribute( QStringLiteral( "id" ) );
2126  if ( !currentLayerId.isEmpty() )
2127  {
2128  QgsMapLayer *currentLayer = mProject->mapLayer( currentLayerId );
2129  if ( currentLayer )
2130  {
2131  QString WMSPropertyAttributesString = currentLayer->customProperty( QStringLiteral( "WMSPropertyAttributes" ) ).toString();
2132  if ( !WMSPropertyAttributesString.isEmpty() )
2133  {
2134  QStringList propertyList = WMSPropertyAttributesString.split( QStringLiteral( "//" ) );
2135  for ( auto propertyIt = propertyList.constBegin() ; propertyIt != propertyList.constEnd(); ++propertyIt )
2136  {
2137  layerPropertyAttributes.insert( *propertyIt );
2138  }
2139  }
2140  }
2141  }
2142 
2143  QDomElement propertyRefChild; //child to insert the next property after (or
2144  for ( int j = 0; j < featureList.size(); ++j )
2145  {
2146  QDomElement SIAFeatureElem = SIAInfoDoc.createElement( currentLayerName );
2147  currentFeatureElem = featureList.at( j ).toElement();
2148  QDomNodeList attributeList = currentFeatureElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2149 
2150  for ( int k = 0; k < attributeList.size(); ++k )
2151  {
2152  currentAttributeElem = attributeList.at( k ).toElement();
2153  currentAttributeName = currentAttributeElem.attribute( QStringLiteral( "name" ) );
2154  currentAttributeValue = currentAttributeElem.attribute( QStringLiteral( "value" ) );
2155  if ( layerPropertyAttributes.contains( currentAttributeName ) )
2156  {
2157  QDomElement propertyElem = SIAInfoDoc.createElement( QStringLiteral( "property" ) );
2158  QDomElement identifierElem = SIAInfoDoc.createElement( QStringLiteral( "identifier" ) );
2159  QDomText identifierText = SIAInfoDoc.createTextNode( currentAttributeName );
2160  identifierElem.appendChild( identifierText );
2161  QDomElement valueElem = SIAInfoDoc.createElement( QStringLiteral( "value" ) );
2162  QDomText valueText = SIAInfoDoc.createTextNode( currentAttributeValue );
2163  valueElem.appendChild( valueText );
2164  propertyElem.appendChild( identifierElem );
2165  propertyElem.appendChild( valueElem );
2166  if ( propertyRefChild.isNull() )
2167  {
2168  SIAFeatureElem.insertBefore( propertyElem, QDomNode() );
2169  propertyRefChild = propertyElem;
2170  }
2171  else
2172  {
2173  SIAFeatureElem.insertAfter( propertyElem, propertyRefChild );
2174  }
2175  }
2176  else
2177  {
2178  QDomElement SIAAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
2179  QDomText SIAAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
2180  SIAAttributeElem.appendChild( SIAAttributeText );
2181  SIAFeatureElem.appendChild( SIAAttributeElem );
2182  }
2183  }
2184  SIAInfoDocElement.appendChild( SIAFeatureElem );
2185  }
2186  }
2187  }
2188  doc = SIAInfoDoc;
2189  }
2190 
2191  QByteArray QgsRenderer::convertFeatureInfoToHtml( const QDomDocument &doc ) const
2192  {
2193  QString featureInfoString;
2194 
2195  //the HTML head
2196  featureInfoString.append( "<HEAD>\n" );
2197  featureInfoString.append( "<TITLE> GetFeatureInfo results </TITLE>\n" );
2198  featureInfoString.append( "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">\n" );
2199  featureInfoString.append( "</HEAD>\n" );
2200 
2201  //start the html body
2202  featureInfoString.append( "<BODY>\n" );
2203 
2204  QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
2205 
2206  //layer loop
2207  for ( int i = 0; i < layerList.size(); ++i )
2208  {
2209  QDomElement layerElem = layerList.at( i ).toElement();
2210 
2211  featureInfoString.append( "<TABLE border=1 width=100%>\n" );
2212  featureInfoString.append( "<TR><TH width=25%>Layer</TH><TD>" + layerElem.attribute( QStringLiteral( "name" ) ) + "</TD></TR>\n" );
2213  featureInfoString.append( "</BR>" );
2214 
2215  //feature loop (for vector layers)
2216  QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2217  QDomElement currentFeatureElement;
2218 
2219  if ( !featureNodeList.isEmpty() ) //vector layer
2220  {
2221  for ( int j = 0; j < featureNodeList.size(); ++j )
2222  {
2223  QDomElement featureElement = featureNodeList.at( j ).toElement();
2224  featureInfoString.append( "<TABLE border=1 width=100%>\n" );
2225  featureInfoString.append( "<TR><TH>Feature</TH><TD>" + featureElement.attribute( QStringLiteral( "id" ) ) +
2226  "</TD></TR>\n" );
2227 
2228  //attribute loop
2229  QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
2230  for ( int k = 0; k < attributeNodeList.size(); ++k )
2231  {
2232  QDomElement attributeElement = attributeNodeList.at( k ).toElement();
2233  featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) +
2234  "</TH><TD>" + attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
2235  }
2236 
2237  featureInfoString.append( "</TABLE>\n</BR>\n" );
2238  }
2239  }
2240  else //raster layer
2241  {
2242  QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2243  for ( int j = 0; j < attributeNodeList.size(); ++j )
2244  {
2245  QDomElement attributeElement = attributeNodeList.at( j ).toElement();
2246  featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) +
2247  "</TH><TD>" + attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
2248  }
2249  }
2250 
2251  featureInfoString.append( "</TABLE>\n<BR></BR>\n" );
2252  }
2253 
2254  //start the html body
2255  featureInfoString.append( "</BODY>\n" );
2256 
2257  return featureInfoString.toUtf8();
2258  }
2259 
2260  QByteArray QgsRenderer::convertFeatureInfoToText( const QDomDocument &doc ) const
2261  {
2262  QString featureInfoString;
2263 
2264  //the Text head
2265  featureInfoString.append( "GetFeatureInfo results\n" );
2266  featureInfoString.append( "\n" );
2267 
2268  QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
2269 
2270  //layer loop
2271  for ( int i = 0; i < layerList.size(); ++i )
2272  {
2273  QDomElement layerElem = layerList.at( i ).toElement();
2274 
2275  featureInfoString.append( "Layer '" + layerElem.attribute( QStringLiteral( "name" ) ) + "'\n" );
2276 
2277  //feature loop (for vector layers)
2278  QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2279  QDomElement currentFeatureElement;
2280 
2281  if ( !featureNodeList.isEmpty() ) //vector layer
2282  {
2283  for ( int j = 0; j < featureNodeList.size(); ++j )
2284  {
2285  QDomElement featureElement = featureNodeList.at( j ).toElement();
2286  featureInfoString.append( "Feature " + featureElement.attribute( QStringLiteral( "id" ) ) + "\n" );
2287 
2288  //attribute loop
2289  QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
2290  for ( int k = 0; k < attributeNodeList.size(); ++k )
2291  {
2292  QDomElement attributeElement = attributeNodeList.at( k ).toElement();
2293  featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
2294  attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
2295  }
2296  }
2297  }
2298  else //raster layer
2299  {
2300  QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2301  for ( int j = 0; j < attributeNodeList.size(); ++j )
2302  {
2303  QDomElement attributeElement = attributeNodeList.at( j ).toElement();
2304  featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
2305  attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
2306  }
2307  }
2308 
2309  featureInfoString.append( "\n" );
2310  }
2311 
2312  return featureInfoString.toUtf8();
2313  }
2314 
2315  QDomElement QgsRenderer::createFeatureGML(
2316  const QgsFeature *feat,
2317  QgsVectorLayer *layer,
2318  QDomDocument &doc,
2320  const QgsMapSettings &mapSettings,
2321  const QString &typeName,
2322  bool withGeom,
2323  int version,
2324  QStringList *attributes ) const
2325  {
2326  //qgs:%TYPENAME%
2327  QDomElement typeNameElement = doc.createElement( "qgs:" + typeName /*qgs:%TYPENAME%*/ );
2328  typeNameElement.setAttribute( QStringLiteral( "fid" ), typeName + "." + QString::number( feat->id() ) );
2329 
2330  QgsCoordinateTransform transform;
2331  if ( layer && layer->crs() != crs )
2332  {
2333  transform = mapSettings.layerTransform( layer );
2334  }
2335 
2336  QgsGeometry geom = feat->geometry();
2337 
2338  QgsExpressionContext expressionContext;
2339  expressionContext << QgsExpressionContextUtils::globalScope()
2341  if ( layer )
2342  expressionContext << QgsExpressionContextUtils::layerScope( layer );
2343  expressionContext.setFeature( *feat );
2344 
2345  // always add bounding box info if feature contains geometry
2346  if ( !geom.isNull() && geom.type() != QgsWkbTypes::UnknownGeometry && geom.type() != QgsWkbTypes::NullGeometry )
2347  {
2348  QgsRectangle box = feat->geometry().boundingBox();
2349  if ( transform.isValid() )
2350  {
2351  try
2352  {
2353  QgsRectangle transformedBox = transform.transformBoundingBox( box );
2354  box = transformedBox;
2355  }
2356  catch ( QgsCsException &e )
2357  {
2358  QgsMessageLog::logMessage( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
2359  }
2360  }
2361 
2362  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
2363  QDomElement boxElem;
2364  if ( version < 3 )
2365  {
2366  boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, getWMSPrecision() );
2367  }
2368  else
2369  {
2370  boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, getWMSPrecision() );
2371  }
2372 
2373  if ( crs.isValid() )
2374  {
2375  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
2376  }
2377  bbElem.appendChild( boxElem );
2378  typeNameElement.appendChild( bbElem );
2379  }
2380 
2381  if ( withGeom && !geom.isNull() )
2382  {
2383  //add geometry column (as gml)
2384 
2385  if ( transform.isValid() )
2386  {
2387  geom.transform( transform );
2388  }
2389 
2390  QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
2391  QDomElement gmlElem;
2392  if ( version < 3 )
2393  {
2394  gmlElem = QgsOgcUtils::geometryToGML( geom, doc, getWMSPrecision() );
2395  }
2396  else
2397  {
2398  gmlElem = QgsOgcUtils::geometryToGML( geom, doc, QStringLiteral( "GML3" ), getWMSPrecision() );
2399  }
2400 
2401  if ( !gmlElem.isNull() )
2402  {
2403  if ( crs.isValid() )
2404  {
2405  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
2406  }
2407  geomElem.appendChild( gmlElem );
2408  typeNameElement.appendChild( geomElem );
2409  }
2410  }
2411 
2412  //read all allowed attribute values from the feature
2413  QgsAttributes featureAttributes = feat->attributes();
2414  QgsFields fields = feat->fields();
2415  for ( int i = 0; i < fields.count(); ++i )
2416  {
2417  QString attributeName = fields.at( i ).name();
2418  //skip attribute if it is explicitly excluded from WMS publication
2419  if ( layer && layer->excludeAttributesWms().contains( attributeName ) )
2420  {
2421  continue;
2422  }
2423  //skip attribute if it is excluded by access control
2424  if ( attributes && !attributes->contains( attributeName ) )
2425  {
2426  continue;
2427  }
2428 
2429  QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ) );
2430  QString fieldTextString = featureAttributes.at( i ).toString();
2431  if ( layer )
2432  {
2433  fieldTextString = QgsExpression::replaceExpressionText( replaceValueMapAndRelation( layer, i, fieldTextString ), &expressionContext );
2434  }
2435  QDomText fieldText = doc.createTextNode( fieldTextString );
2436  fieldElem.appendChild( fieldText );
2437  typeNameElement.appendChild( fieldElem );
2438  }
2439 
2440  //add maptip attribute based on html/expression (in case there is no maptip attribute)
2441  if ( layer )
2442  {
2443  QString mapTip = layer->mapTipTemplate();
2444 
2445  if ( !mapTip.isEmpty() && mWmsParameters.withMapTip() )
2446  {
2447  QString fieldTextString = QgsExpression::replaceExpressionText( mapTip, &expressionContext );
2448  QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:maptip" ) );
2449  QDomText maptipText = doc.createTextNode( fieldTextString );
2450  fieldElem.appendChild( maptipText );
2451  typeNameElement.appendChild( fieldElem );
2452  }
2453  }
2454 
2455  return typeNameElement;
2456  }
2457 
2458  QString QgsRenderer::replaceValueMapAndRelation( QgsVectorLayer *vl, int idx, const QVariant &attributeVal )
2459  {
2460  const QgsEditorWidgetSetup setup = vl->editorWidgetSetup( idx );
2462  QString value( fieldFormatter->representValue( vl, idx, setup.config(), QVariant(), attributeVal ) );
2463 
2464  if ( setup.config().value( QStringLiteral( "AllowMulti" ) ).toBool() && value.startsWith( QLatin1String( "{" ) ) && value.endsWith( QLatin1String( "}" ) ) )
2465  {
2466  value = value.mid( 1, value.size() - 2 );
2467  }
2468  return value;
2469  }
2470 
2472  {
2473  // First taken from QGIS project
2474  int imageQuality = QgsServerProjectUtils::wmsImageQuality( *mProject );
2475 
2476  // Then checks if a parameter is given, if so use it instead
2477  if ( !mWmsParameters.imageQuality().isEmpty() )
2478  {
2479  imageQuality = mWmsParameters.imageQualityAsInt();
2480  }
2481 
2482  return imageQuality;
2483  }
2484 
2486  {
2487  // First taken from QGIS project and the default value is 6
2488  int WMSPrecision = QgsServerProjectUtils::wmsFeatureInfoPrecision( *mProject );
2489 
2490  // Then checks if a parameter is given, if so use it instead
2491  int WMSPrecisionParameter = mWmsParameters.wmsPrecisionAsInt();
2492 
2493  if ( WMSPrecisionParameter > -1 )
2494  return WMSPrecisionParameter;
2495  else
2496  return WMSPrecision;
2497  }
2498 
2499  QgsRectangle QgsRenderer::featureInfoSearchRect( QgsVectorLayer *ml, const QgsMapSettings &mapSettings, const QgsRenderContext &rct, const QgsPointXY &infoPoint ) const
2500  {
2501  if ( !ml )
2502  {
2503  return QgsRectangle();
2504  }
2505 
2506  double mapUnitTolerance = 0.0;
2508  {
2509  if ( ! mWmsParameters.polygonTolerance().isEmpty()
2510  && mWmsParameters.polygonToleranceAsInt() > 0 )
2511  {
2512  mapUnitTolerance = mWmsParameters.polygonToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
2513  }
2514  else
2515  {
2516  mapUnitTolerance = mapSettings.extent().width() / 400.0;
2517  }
2518  }
2519  else if ( ml->geometryType() == QgsWkbTypes::LineGeometry )
2520  {
2521  if ( ! mWmsParameters.lineTolerance().isEmpty()
2522  && mWmsParameters.lineToleranceAsInt() > 0 )
2523  {
2524  mapUnitTolerance = mWmsParameters.lineToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
2525  }
2526  else
2527  {
2528  mapUnitTolerance = mapSettings.extent().width() / 200.0;
2529  }
2530  }
2531  else //points
2532  {
2533  if ( ! mWmsParameters.pointTolerance().isEmpty()
2534  && mWmsParameters.pointToleranceAsInt() > 0 )
2535  {
2536  mapUnitTolerance = mWmsParameters.pointToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
2537  }
2538  else
2539  {
2540  mapUnitTolerance = mapSettings.extent().width() / 100.0;
2541  }
2542  }
2543 
2544  QgsRectangle mapRectangle( infoPoint.x() - mapUnitTolerance, infoPoint.y() - mapUnitTolerance,
2545  infoPoint.x() + mapUnitTolerance, infoPoint.y() + mapUnitTolerance );
2546  return ( mapSettings.mapToLayerCoordinates( ml, mapRectangle ) );
2547  }
2548 
2549 
2550  void QgsRenderer::initRestrictedLayers()
2551  {
2552  mRestrictedLayers.clear();
2553 
2554  // get name of restricted layers/groups in project
2555  QStringList restricted = QgsServerProjectUtils::wmsRestrictedLayers( *mProject );
2556 
2557  // extract restricted layers from excluded groups
2558  QStringList restrictedLayersNames;
2559  QgsLayerTreeGroup *root = mProject->layerTreeRoot();
2560 
2561  for ( const QString &l : restricted )
2562  {
2563  QgsLayerTreeGroup *group = root->findGroup( l );
2564  if ( group )
2565  {
2566  QList<QgsLayerTreeLayer *> groupLayers = group->findLayers();
2567  for ( QgsLayerTreeLayer *treeLayer : groupLayers )
2568  {
2569  restrictedLayersNames.append( treeLayer->name() );
2570  }
2571  }
2572  else
2573  {
2574  restrictedLayersNames.append( l );
2575  }
2576  }
2577 
2578  // build output with names, ids or short name according to the configuration
2579  QList<QgsLayerTreeLayer *> layers = root->findLayers();
2580  for ( QgsLayerTreeLayer *layer : layers )
2581  {
2582  if ( restrictedLayersNames.contains( layer->name() ) )
2583  {
2584  mRestrictedLayers.append( layerNickname( *layer->layer() ) );
2585  }
2586  }
2587  }
2588 
2589  void QgsRenderer::initNicknameLayers()
2590  {
2591  for ( QgsMapLayer *ml : mProject->mapLayers() )
2592  {
2593  mNicknameLayers[ layerNickname( *ml ) ] = ml;
2594  }
2595 
2596  // init groups
2597  const QString rootName { QgsServerProjectUtils::wmsRootName( *mProject ) };
2598  const QgsLayerTreeGroup *root = mProject->layerTreeRoot();
2599  initLayerGroupsRecursive( root, rootName.isEmpty() ? mProject->title() : rootName );
2600  }
2601 
2602  void QgsRenderer::initLayerGroupsRecursive( const QgsLayerTreeGroup *group, const QString &groupName )
2603  {
2604  if ( !groupName.isEmpty() )
2605  {
2606  mLayerGroups[groupName] = QList<QgsMapLayer *>();
2607  const auto projectLayerTreeRoot { mProject->layerTreeRoot() };
2608  const auto treeGroupLayers { group->findLayers() };
2609  // Fast track if there is no custom layer order,
2610  // otherwise reorder layers.
2611  if ( ! projectLayerTreeRoot->hasCustomLayerOrder() )
2612  {
2613  for ( const auto &tl : treeGroupLayers )
2614  {
2615  mLayerGroups[groupName].push_back( tl->layer() );
2616  }
2617  }
2618  else
2619  {
2620  const auto projectLayerOrder { projectLayerTreeRoot->layerOrder() };
2621  // Flat list containing the layers from the tree nodes
2622  QList<QgsMapLayer *> groupLayersList;
2623  for ( const auto &tl : treeGroupLayers )
2624  {
2625  groupLayersList << tl->layer();
2626  }
2627  for ( const auto &l : projectLayerOrder )
2628  {
2629  if ( groupLayersList.contains( l ) )
2630  {
2631  mLayerGroups[groupName].push_back( l );
2632  }
2633  }
2634  }
2635  }
2636 
2637  for ( const QgsLayerTreeNode *child : group->children() )
2638  {
2639  if ( child->nodeType() == QgsLayerTreeNode::NodeGroup )
2640  {
2641  QString name = child->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
2642 
2643  if ( name.isEmpty() )
2644  name = child->name();
2645 
2646  initLayerGroupsRecursive( static_cast<const QgsLayerTreeGroup *>( child ), name );
2647 
2648  }
2649  }
2650  }
2651 
2652  QString QgsRenderer::layerNickname( const QgsMapLayer &layer ) const
2653  {
2654  QString name = layer.shortName();
2655  if ( QgsServerProjectUtils::wmsUseLayerIds( *mProject ) )
2656  {
2657  name = layer.id();
2658  }
2659  else if ( name.isEmpty() )
2660  {
2661  name = layer.name();
2662  }
2663 
2664  return name;
2665  }
2666 
2667  bool QgsRenderer::layerScaleVisibility( const QgsMapLayer &layer, double scaleDenominator ) const
2668  {
2669  bool visible = false;
2670  bool scaleBasedVisibility = layer.hasScaleBasedVisibility();
2671  bool useScaleConstraint = ( scaleDenominator > 0 && scaleBasedVisibility );
2672 
2673  if ( !useScaleConstraint || layer.isInScaleRange( scaleDenominator ) )
2674  {
2675  visible = true;
2676  }
2677 
2678  return visible;
2679  }
2680 
2681  QList<QgsMapLayer *> QgsRenderer::highlightLayers( QList<QgsWmsParametersHighlightLayer> params )
2682  {
2683  QList<QgsMapLayer *> highlightLayers;
2684 
2685  // try to create highlight layer for each geometry
2686  QString crs = mWmsParameters.crs();
2687  for ( const QgsWmsParametersHighlightLayer &param : params )
2688  {
2689  // create sld document from symbology
2690  QDomDocument sldDoc;
2691  if ( !sldDoc.setContent( param.mSld, true ) )
2692  {
2693  continue;
2694  }
2695 
2696  // create renderer from sld document
2697  QString errorMsg;
2698  std::unique_ptr<QgsFeatureRenderer> renderer;
2699  QDomElement el = sldDoc.documentElement();
2700  renderer.reset( QgsFeatureRenderer::loadSld( el, param.mGeom.type(), errorMsg ) );
2701  if ( !renderer )
2702  {
2703  QgsMessageLog::logMessage( errorMsg, "Server", Qgis::Info );
2704  continue;
2705  }
2706 
2707  // build url for vector layer
2708  const QString typeName = QgsWkbTypes::displayString( param.mGeom.wkbType() );
2709  QString url = typeName + "?crs=" + crs;
2710  if ( ! param.mLabel.isEmpty() )
2711  {
2712  url += "&field=label:string";
2713  }
2714 
2715  // create vector layer
2716  std::unique_ptr<QgsVectorLayer> layer;
2717  layer.reset( new QgsVectorLayer( url, param.mName, "memory" ) );
2718  if ( !layer->isValid() )
2719  {
2720  continue;
2721  }
2722 
2723  // create feature with label if necessary
2724  QgsFeature fet( layer->fields() );
2725  if ( ! param.mLabel.isEmpty() )
2726  {
2727  fet.setAttribute( 0, param.mLabel );
2728 
2729  // init labeling engine
2730  QgsPalLayerSettings palSettings;
2731  palSettings.fieldName = "label"; // defined in url
2732  palSettings.priority = 10; // always drawn
2733  palSettings.displayAll = true;
2734 
2736  switch ( param.mGeom.type() )
2737  {
2739  {
2741  palSettings.dist = 2; // in mm
2742  palSettings.placementFlags = 0;
2743  break;
2744  }
2746  {
2747  QgsGeometry point = param.mGeom.pointOnSurface();
2748  QgsPointXY pt = point.asPoint();
2750 
2752  QVariant x( pt.x() );
2753  palSettings.dataDefinedProperties().setProperty( pX, x );
2754 
2756  QVariant y( pt.y() );
2757  palSettings.dataDefinedProperties().setProperty( pY, y );
2758 
2760  QVariant hali( "Center" );
2761  palSettings.dataDefinedProperties().setProperty( pHali, hali );
2762 
2764  QVariant vali( "Half" );
2765  palSettings.dataDefinedProperties().setProperty( pVali, vali );
2766  break;
2767  }
2768  default:
2769  {
2770  placement = QgsPalLayerSettings::Line;
2771  palSettings.dist = 2;
2772  palSettings.placementFlags = 10;
2773  break;
2774  }
2775  }
2776  palSettings.placement = placement;
2777  QgsTextFormat textFormat;
2778  QgsTextBufferSettings bufferSettings;
2779 
2780  if ( param.mColor.isValid() )
2781  {
2782  textFormat.setColor( param.mColor );
2783  }
2784 
2785  if ( param.mSize > 0 )
2786  {
2787  textFormat.setSize( param.mSize );
2788  }
2789 
2790  // no weight property in PAL settings or QgsTextFormat
2791  /* if ( param.fontWeight > 0 )
2792  {
2793  } */
2794 
2795  if ( ! param.mFont.isEmpty() )
2796  {
2797  textFormat.setFont( param.mFont );
2798  }
2799 
2800  if ( param.mBufferColor.isValid() )
2801  {
2802  bufferSettings.setColor( param.mBufferColor );
2803  }
2804 
2805  if ( param.mBufferSize > 0 )
2806  {
2807  bufferSettings.setEnabled( true );
2808  bufferSettings.setSize( param.mBufferSize );
2809  }
2810 
2811  textFormat.setBuffer( bufferSettings );
2812  palSettings.setFormat( textFormat );
2813 
2814  QgsVectorLayerSimpleLabeling *simpleLabeling = new QgsVectorLayerSimpleLabeling( palSettings );
2815  layer->setLabeling( simpleLabeling );
2816  layer->setLabelsEnabled( true );
2817  }
2818  fet.setGeometry( param.mGeom );
2819 
2820  // add feature to layer and set the SLD renderer
2821  layer->dataProvider()->addFeatures( QgsFeatureList() << fet );
2822  layer->setRenderer( renderer.release() );
2823 
2824  // keep the vector as an highlight layer
2825  if ( layer->isValid() )
2826  {
2827  highlightLayers.append( layer.release() );
2828  }
2829  }
2830 
2831  mTemporaryLayers.append( highlightLayers );
2832  return highlightLayers;
2833  }
2834 
2835  QList<QgsMapLayer *> QgsRenderer::sldStylizedLayers( const QString &sld ) const
2836  {
2837  QList<QgsMapLayer *> layers;
2838 
2839  if ( !sld.isEmpty() )
2840  {
2841  QDomDocument doc;
2842  ( void )doc.setContent( sld, true );
2843  QDomElement docEl = doc.documentElement();
2844 
2845  QDomElement root = doc.firstChildElement( "StyledLayerDescriptor" );
2846  QDomElement namedElem = root.firstChildElement( "NamedLayer" );
2847 
2848  if ( !docEl.isNull() )
2849  {
2850  QDomNodeList named = docEl.elementsByTagName( "NamedLayer" );
2851  for ( int i = 0; i < named.size(); ++i )
2852  {
2853  QDomNodeList names = named.item( i ).toElement().elementsByTagName( "Name" );
2854  if ( !names.isEmpty() )
2855  {
2856  QString lname = names.item( 0 ).toElement().text();
2857  QString err;
2858  if ( mNicknameLayers.contains( lname ) && !mRestrictedLayers.contains( lname ) )
2859  {
2860  mNicknameLayers[lname]->readSld( namedElem, err );
2861  mNicknameLayers[lname]->setCustomProperty( "readSLD", true );
2862  layers.append( mNicknameLayers[lname] );
2863  }
2864  else if ( mLayerGroups.contains( lname ) )
2865  {
2866  for ( QgsMapLayer *layer : mLayerGroups[lname] )
2867  {
2868  if ( !mRestrictedLayers.contains( layerNickname( *layer ) ) )
2869  {
2870  layer->readSld( namedElem, err );
2871  layer->setCustomProperty( "readSLD", true );
2872  layers.insert( 0, layer );
2873  }
2874  }
2875  }
2876  else
2877  {
2878  throw QgsBadRequestException( QStringLiteral( "LayerNotDefined" ),
2879  QStringLiteral( "Layer \"%1\" does not exist" ).arg( lname ) );
2880  }
2881  }
2882  }
2883  }
2884  }
2885 
2886  return layers;
2887  }
2888 
2889  QList<QgsMapLayer *> QgsRenderer::stylizedLayers( const QList<QgsWmsParametersLayer> &params )
2890  {
2891  QList<QgsMapLayer *> layers;
2892 
2893  for ( const QgsWmsParametersLayer &param : params )
2894  {
2895  QString nickname = param.mNickname;
2896  QString style = param.mStyle;
2897  if ( nickname.startsWith( "EXTERNAL_WMS:" ) )
2898  {
2899  QString externalLayerId = nickname;
2900  externalLayerId.remove( 0, 13 );
2901  QgsMapLayer *externalWMSLayer = createExternalWMSLayer( externalLayerId );
2902  if ( externalWMSLayer )
2903  {
2904  layers.append( externalWMSLayer );
2905  mNicknameLayers[nickname] = externalWMSLayer; //might be used later in GetPrint request
2906  mTemporaryLayers.append( externalWMSLayer );
2907  }
2908  }
2909  else if ( mNicknameLayers.contains( nickname ) && !mRestrictedLayers.contains( nickname ) )
2910  {
2911  if ( !style.isEmpty() )
2912  {
2913  bool rc = mNicknameLayers[nickname]->styleManager()->setCurrentStyle( style );
2914  if ( ! rc )
2915  {
2916  throw QgsMapServiceException( QStringLiteral( "StyleNotDefined" ), QStringLiteral( "Style \"%1\" does not exist for layer \"%2\"" ).arg( style, nickname ) );
2917  }
2918  }
2919 
2920  layers.append( mNicknameLayers[nickname] );
2921  }
2922  else if ( mLayerGroups.contains( nickname ) )
2923  {
2924  // Reverse order of layers from a group
2925  QList<QgsMapLayer *> layersFromGroup;
2926  for ( QgsMapLayer *layer : mLayerGroups[nickname] )
2927  {
2928  if ( !mRestrictedLayers.contains( layerNickname( *layer ) ) )
2929  {
2930  if ( !style.isEmpty() )
2931  {
2932  bool rc = layer->styleManager()->setCurrentStyle( style );
2933  if ( ! rc )
2934  {
2935  throw QgsMapServiceException( QStringLiteral( "StyleNotDefined" ), QStringLiteral( "Style \"%1\" does not exist for layer \"%2\"" ).arg( style, layerNickname( *layer ) ) );
2936  }
2937  }
2938  layersFromGroup.push_front( layer );
2939  }
2940  }
2941  layers.append( layersFromGroup );
2942  }
2943  else
2944  {
2945  throw QgsBadRequestException( QStringLiteral( "LayerNotDefined" ),
2946  QStringLiteral( "Layer \"%1\" does not exist" ).arg( nickname ) );
2947  }
2948  }
2949 
2950  return layers;
2951  }
2952 
2953  QgsMapLayer *QgsRenderer::createExternalWMSLayer( const QString &externalLayerId ) const
2954  {
2955  QString wmsUri = mWmsParameters.externalWMSUri( externalLayerId.toUpper() );
2956  QgsMapLayer *wmsLayer = new QgsRasterLayer( wmsUri, externalLayerId, QStringLiteral( "wms" ) );
2957  if ( !wmsLayer->isValid() )
2958  {
2959  delete wmsLayer;
2960  return nullptr;
2961  }
2962 
2963  return wmsLayer;
2964  }
2965 
2966  void QgsRenderer::removeTemporaryLayers()
2967  {
2968  qDeleteAll( mTemporaryLayers );
2969  mTemporaryLayers.clear();
2970  }
2971 
2972  QPainter *QgsRenderer::layersRendering( const QgsMapSettings &mapSettings, QImage &image, HitTest *hitTest ) const
2973  {
2974  QPainter *painter = nullptr;
2975  if ( hitTest )
2976  {
2977  runHitTest( mapSettings, *hitTest );
2978  painter = new QPainter();
2979  }
2980  else
2981  {
2983  filters.addProvider( &mFeatureFilter );
2984 #ifdef HAVE_SERVER_PYTHON_PLUGINS
2985  mAccessControl->resolveFilterFeatures( mapSettings.layers() );
2986  filters.addProvider( mAccessControl );
2987 #endif
2988  QgsMapRendererJobProxy renderJob( mSettings.parallelRendering(), mSettings.maxThreads(), &filters );
2989  renderJob.render( mapSettings, &image );
2990  painter = renderJob.takePainter();
2991  }
2992 
2993  return painter;
2994  }
2995 
2996  void QgsRenderer::setLayerOpacity( QgsMapLayer *layer, int opacity ) const
2997  {
2998  if ( opacity >= 0 && opacity <= 255 )
2999  {
3000  if ( layer->type() == QgsMapLayer::LayerType::VectorLayer )
3001  {
3002  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
3003  vl->setOpacity( opacity / 255. );
3004  }
3005  else if ( layer->type() == QgsMapLayer::LayerType::RasterLayer )
3006  {
3007  QgsRasterLayer *rl = qobject_cast<QgsRasterLayer *>( layer );
3008  QgsRasterRenderer *rasterRenderer = rl->renderer();
3009  rasterRenderer->setOpacity( opacity / 255. );
3010  }
3011  }
3012  }
3013 
3014  void QgsRenderer::setLayerFilter( QgsMapLayer *layer, const QList<QgsWmsParametersFilter> &filters )
3015  {
3016  if ( layer->type() == QgsMapLayer::VectorLayer )
3017  {
3018  QgsVectorLayer *filteredLayer = qobject_cast<QgsVectorLayer *>( layer );
3019  for ( const QgsWmsParametersFilter &filter : filters )
3020  {
3021  if ( filter.mType == QgsWmsParametersFilter::OGC_FE )
3022  {
3023  // OGC filter
3024  QDomDocument filterXml;
3025  QString errorMsg;
3026  if ( !filterXml.setContent( filter.mFilter, true, &errorMsg ) )
3027  {
3028  throw QgsBadRequestException( QStringLiteral( "Filter string rejected" ),
3029  QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, filter.mFilter ) );
3030  }
3031  QDomElement filterElem = filterXml.firstChildElement();
3032  std::unique_ptr<QgsExpression> expression( QgsOgcUtils::expressionFromOgcFilter( filterElem, filter.mVersion, filteredLayer ) );
3033 
3034  if ( expression )
3035  {
3036  mFeatureFilter.setFilter( filteredLayer, *expression );
3037  }
3038  }
3039  else if ( filter.mType == QgsWmsParametersFilter::SQL )
3040  {
3041  // QGIS (SQL) filter
3042  if ( !testFilterStringSafety( filter.mFilter ) )
3043  {
3044  throw QgsBadRequestException( QStringLiteral( "Filter string rejected" ),
3045  QStringLiteral( "The filter string %1"
3046  " has been rejected because of security reasons."
3047  " Note: Text strings have to be enclosed in single or double quotes."
3048  " A space between each word / special character is mandatory."
3049  " Allowed Keywords and special characters are "
3050  " IS,NOT,NULL,AND,OR,IN,=,<,>=,>,>=,!=,',',(,),DMETAPHONE,SOUNDEX."
3051  " Not allowed are semicolons in the filter expression." ).arg(
3052  filter.mFilter ) );
3053  }
3054 
3055  QString newSubsetString = filter.mFilter;
3056  if ( !filteredLayer->subsetString().isEmpty() )
3057  {
3058  newSubsetString.prepend( ") AND (" );
3059  newSubsetString.append( ")" );
3060  newSubsetString.prepend( filteredLayer->subsetString() );
3061  newSubsetString.prepend( "(" );
3062  }
3063  filteredLayer->setSubsetString( newSubsetString );
3064  }
3065  }
3066  }
3067  }
3068 
3069  void QgsRenderer::setLayerSelection( QgsMapLayer *layer, const QStringList &fids ) const
3070  {
3071  if ( layer->type() == QgsMapLayer::VectorLayer )
3072  {
3073  QgsFeatureIds selectedIds;
3074 
3075  for ( const QString &id : fids )
3076  {
3077  selectedIds.insert( STRING_TO_FID( id ) );
3078  }
3079 
3080  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
3081  vl->selectByIds( selectedIds );
3082  }
3083  }
3084 
3085  void QgsRenderer::setLayerAccessControlFilter( QgsMapLayer *layer ) const
3086  {
3087 #ifdef HAVE_SERVER_PYTHON_PLUGINS
3089 #else
3090  Q_UNUSED( layer );
3091 #endif
3092  }
3093 
3094  void QgsRenderer::updateExtent( const QgsMapLayer *layer, QgsMapSettings &mapSettings ) const
3095  {
3096  QgsRectangle layerExtent = mapSettings.layerToMapCoordinates( layer, layer->extent() );
3097  QgsRectangle mapExtent = mapSettings.extent();
3098  if ( !layerExtent.isEmpty() )
3099  {
3100  mapExtent.combineExtentWith( layerExtent );
3101  mapSettings.setExtent( mapExtent );
3102  }
3103  }
3104 
3105  void QgsRenderer::annotationsRendering( QPainter *painter ) const
3106  {
3107  const QgsAnnotationManager *annotationManager = mProject->annotationManager();
3108  const QList< QgsAnnotation * > annotations = annotationManager->annotations();
3109 
3110  QgsRenderContext renderContext = QgsRenderContext::fromQPainter( painter );
3111  for ( QgsAnnotation *annotation : annotations )
3112  {
3113  if ( !annotation || !annotation->isVisible() )
3114  continue;
3115 
3116  annotation->render( renderContext );
3117  }
3118  }
3119 
3120  QImage *QgsRenderer::scaleImage( const QImage *image ) const
3121  {
3122  //test if width / height ratio of image is the same as the ratio of
3123  // WIDTH / HEIGHT parameters. If not, the image has to be scaled (required
3124  // by WMS spec)
3125  QImage *scaledImage = nullptr;
3126  int width = this->width();
3127  int height = this->height();
3128  if ( width != image->width() || height != image->height() )
3129  {
3130  scaledImage = new QImage( image->scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
3131  }
3132 
3133  return scaledImage;
3134  }
3135 
3136  void QgsRenderer::checkLayerReadPermissions( QgsMapLayer *layer ) const
3137  {
3138 #ifdef HAVE_SERVER_PYTHON_PLUGINS
3139  if ( !mAccessControl->layerReadPermission( layer ) )
3140  {
3141  throw QgsSecurityException( QStringLiteral( "You are not allowed to access to the layer: %1" ).arg( layer->name() ) );
3142  }
3143 #else
3144  Q_UNUSED( layer );
3145 #endif
3146  }
3147 
3148  void QgsRenderer::removeUnwantedLayers( QList<QgsMapLayer *> &layers, double scaleDenominator ) const
3149  {
3150  QList<QgsMapLayer *> wantedLayers;
3151 
3152  for ( QgsMapLayer *layer : layers )
3153  {
3154  if ( !layerScaleVisibility( *layer, scaleDenominator ) )
3155  continue;
3156 
3157  if ( mRestrictedLayers.contains( layerNickname( *layer ) ) )
3158  continue;
3159 
3160  wantedLayers.append( layer );
3161  }
3162 
3163  layers = wantedLayers;
3164  }
3165 
3166  void QgsRenderer::removeNonIdentifiableLayers( QList<QgsMapLayer *> &layers ) const
3167  {
3168  QList<QgsMapLayer *>::iterator it = layers.begin();
3169  while ( it != layers.end() )
3170  {
3171  if ( !( *it )->flags().testFlag( QgsMapLayer::Identifiable ) )
3172  it = layers.erase( it );
3173  else
3174  ++it;
3175  }
3176  }
3177 
3178  QgsLayerTreeModel *QgsRenderer::buildLegendTreeModel( const QList<QgsMapLayer *> &layers, double scaleDenominator, QgsLayerTree &rootGroup )
3179  {
3180  // get params
3181  bool showFeatureCount = mWmsParameters.showFeatureCountAsBool();
3182  bool drawLegendLayerLabel = mWmsParameters.layerTitleAsBool();
3183  bool drawLegendItemLabel = mWmsParameters.ruleLabelAsBool();
3184 
3185  bool ruleDefined = false;
3186  if ( !mWmsParameters.rule().isEmpty() )
3187  ruleDefined = true;
3188 
3189  bool contentBasedLegend = false;
3190  QgsRectangle contentBasedLegendExtent;
3191  if ( ! mWmsParameters.bbox().isEmpty() )
3192  {
3193  contentBasedLegend = true;
3194  contentBasedLegendExtent = mWmsParameters.bboxAsRectangle();
3195  if ( contentBasedLegendExtent.isEmpty() )
3196  throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ),
3197  QStringLiteral( "Invalid BBOX parameter" ) );
3198 
3199  if ( !mWmsParameters.rule().isEmpty() )
3200  throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ),
3201  QStringLiteral( "BBOX parameter cannot be combined with RULE" ) );
3202  }
3203 
3204  // build layer tree
3205  rootGroup.clear();
3206  QList<QgsVectorLayerFeatureCounter *> counters;
3207  for ( QgsMapLayer *ml : layers )
3208  {
3209  QgsLayerTreeLayer *lt = rootGroup.addLayer( ml );
3210  lt->setCustomProperty( QStringLiteral( "showFeatureCount" ), showFeatureCount );
3211 
3212  if ( !ml->title().isEmpty() )
3213  lt->setName( ml->title() );
3214 
3215  if ( ml->type() != QgsMapLayer::VectorLayer || !showFeatureCount )
3216  continue;
3217 
3218  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
3220  if ( !counter )
3221  continue;
3222  counters.append( counter );
3223  }
3224 
3225  // build legend model
3226  QgsLayerTreeModel *legendModel = new QgsLayerTreeModel( &rootGroup );
3227  if ( scaleDenominator > 0 )
3228  legendModel->setLegendFilterByScale( scaleDenominator );
3229 
3230  QgsMapSettings contentBasedMapSettings;
3231  if ( contentBasedLegend )
3232  {
3233  HitTest hitTest;
3234  getMap( contentBasedMapSettings, &hitTest );
3235 
3236  for ( QgsLayerTreeNode *node : rootGroup.children() )
3237  {
3238  Q_ASSERT( QgsLayerTree::isLayer( node ) );
3239  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
3240 
3241  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
3242  if ( !vl || !vl->renderer() )
3243  continue;
3244 
3245  const SymbolSet &usedSymbols = hitTest[vl];
3246  QList<int> order;
3247  int i = 0;
3248  for ( const QgsLegendSymbolItem &legendItem : vl->renderer()->legendSymbolItems() )
3249  {
3250  QString sProp = QgsSymbolLayerUtils::symbolProperties( legendItem.legacyRuleKey() );
3251  if ( usedSymbols.contains( sProp ) )
3252  order.append( i );
3253  ++i;
3254  }
3255 
3256  // either remove the whole layer or just filter out some items
3257  if ( order.isEmpty() )
3258  rootGroup.removeChildNode( nodeLayer );
3259  else
3260  {
3261  QgsMapLayerLegendUtils::setLegendNodeOrder( nodeLayer, order );
3262  legendModel->refreshLayerLegend( nodeLayer );
3263  }
3264  }
3265  }
3266 
3267  // if legend is not based on rendering rules
3268  if ( ! ruleDefined )
3269  {
3270  QList<QgsLayerTreeNode *> rootChildren = rootGroup.children();
3271  for ( QgsLayerTreeNode *node : rootChildren )
3272  {
3273  if ( QgsLayerTree::isLayer( node ) )
3274  {
3275  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
3276 
3277  // layer titles - hidden or not
3279 
3280  // rule item titles
3281  if ( !drawLegendItemLabel )
3282  {
3283  for ( QgsLayerTreeModelLegendNode *legendNode : legendModel->layerLegendNodes( nodeLayer ) )
3284  {
3285  legendNode->setUserLabel( QStringLiteral( " " ) ); // empty string = no override, so let's use one space
3286  }
3287  }
3288  else if ( !drawLegendLayerLabel )
3289  {
3290  for ( QgsLayerTreeModelLegendNode *legendNode : legendModel->layerLegendNodes( nodeLayer ) )
3291  {
3292  if ( legendNode->isEmbeddedInParent() )
3293  legendNode->setEmbeddedInParent( false );
3294  }
3295  }
3296  }
3297  }
3298  }
3299 
3300  for ( QgsVectorLayerFeatureCounter *c : counters )
3301  {
3302  c->waitForFinished();
3303  }
3304 
3305  return legendModel;
3306  }
3307 
3308  qreal QgsRenderer::dotsPerMm() const
3309  {
3310  std::unique_ptr<QImage> tmpImage( createImage( 1, 1, false ) );
3311  return tmpImage->dotsPerMeterX() / 1000.0;
3312  }
3313 
3314  QStringList QgsRenderer::flattenedQueryLayers() const
3315  {
3316  QStringList result;
3317  std::function <QStringList( const QString &name )> findLeaves = [ & ]( const QString & name ) -> QStringList
3318  {
3319  QStringList _result;
3320  if ( mLayerGroups.contains( name ) )
3321  {
3322  const auto &layers { mLayerGroups[ name ] };
3323  for ( const auto &l : layers )
3324  {
3325  const auto nick { layerNickname( *l ) };
3326  if ( mLayerGroups.contains( nick ) )
3327  {
3328  _result.append( name );
3329  }
3330  else
3331  {
3332  _result.append( findLeaves( nick ) );
3333  }
3334  }
3335  }
3336  else
3337  {
3338  _result.append( name );
3339  }
3340  return _result;
3341  };
3342  const auto constNicks { mWmsParameters.queryLayersNickname() };
3343  for ( const auto &name : constNicks )
3344  {
3345  result.append( findLeaves( name ) );
3346  }
3347  return result;
3348  }
3349 
3350  int QgsRenderer::height() const
3351  {
3352  if ( ( mWmsParameters.request().compare( QStringLiteral( "GetLegendGraphic" ), Qt::CaseInsensitive ) == 0 ||
3353  mWmsParameters.request().compare( QStringLiteral( "GetLegendGraphics" ), Qt::CaseInsensitive ) == 0 ) &&
3354  mWmsParameters.srcHeightAsInt() > 0 )
3355  return mWmsParameters.srcHeightAsInt();
3356  return mWmsParameters.heightAsInt();
3357  }
3358 
3359  int QgsRenderer::width() const
3360  {
3361  if ( ( mWmsParameters.request().compare( QStringLiteral( "GetLegendGraphic" ), Qt::CaseInsensitive ) == 0 ||
3362  mWmsParameters.request().compare( QStringLiteral( "GetLegendGraphics" ), Qt::CaseInsensitive ) == 0 ) &&
3363  mWmsParameters.srcWidthAsInt() > 0 )
3364  return mWmsParameters.srcWidthAsInt();
3365  return mWmsParameters.widthAsInt();
3366  }
3367 } // namespace QgsWms
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
Export only data.
Definition: qgsdxfexport.h:82
QgsPrintLayout * clone() const override
Creates a clone of the layout.
static void setNodeLegendStyle(QgsLayerTreeNode *node, QgsLegendStyle::Style style)
QByteArray getFeatureInfo(const QString &version="1.3.0")
Creates an xml document that describes the result of the getFeatureInfo request.
int pageCount() const
Returns the number of pages in the collection.
void updateFields()
Will regenerate the fields property of this layer by obtaining all fields from the dataProvider...
Layer tree group node serves as a container for layers and further groups.
QgsFeatureId id
Definition: qgsfeature.h:64
bool isValid() const
Returns true if valid.
Wrapper for iterator of features from vector data provider or vector layer.
A container for features with the same fields and crs.
int widthAsInt() const
Returns WIDTH parameter as an int or its default value if not defined.
IdentifyFormat
Definition: qgsraster.h:57
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
void removeChildrenGroupWithoutLayers()
Remove all child group nodes without layers.
May use more than one symbol to render a feature: symbolsForFeature() will return them...
Definition: qgsrenderer.h:243
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
A rectangle specified with double values.
Definition: qgsrectangle.h:40
Base class for all map layer types.
Definition: qgsmaplayer.h:63
void setExtent(const QgsRectangle &rect, bool magnified=true)
Set coordinates of the rectangle which should be rendered.
QgsGeometry pointOnSurface() const
Returns a point guaranteed to lie on the surface of a geometry.
QString title() const
Returns the project&#39;s title.
Definition: qgsproject.cpp:431
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:425
static void setLegendNodeOrder(QgsLayerTreeLayer *nodeLayer, const QList< int > &order)
QgsVectorLayerFeatureCounter * countSymbolFeatures()
Count features for symbols.
const Flags & flags() const
Item model implementation based on layer tree model for layout legend.
QString sldBody() const
Returns SLD_body if defined or an empty string.
SERVER_EXPORT int wmsMaxWidth(const QgsProject &project)
Returns the maximum width for WMS images defined in a QGIS project.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
QgsMapLayer::LayerType type() const
Returns the type of the layer.
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
Definition: qgsmaplayer.h:257
bool isValid() const
Returns the status of the layer.
QList< QgsAnnotation * > annotations() const
Returns a list of all annotations contained in the manager.
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non-null pointer.
double scale() const
Returns the calculated map scale.
void setFields(const QgsFields &fields, bool initAttributes=false)
Assign a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:162
QString name
Definition: qgsfield.h:57
Manages storage of a set of QgsAnnotation annotation objects.
bool isNull() const
Returns true if the geometry is null (ie, contains no underlying geometry accessible via geometry() )...
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Use exact geometry intersection (slower) instead of bounding boxes.
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer&#39;s CRS to output CRS
virtual QgsAttributeList pkAttributeIndexes() const
Returns list of indexes of fields that make up the primary key.
A layout item subclass for text labels.
void setCoordinateTransform(const QgsCoordinateTransform &t)
Sets the current coordinate transform for the context.
QStringList queryLayersNickname() const
Returns nickname of layers found in QUERY_LAYERS parameter.
int getWMSPrecision() const
Returns the precision to use for GetFeatureInfo request.
SERVER_EXPORT double wmsDefaultMapUnitsPerMm(const QgsProject &project)
Returns the default number of map units per millimeters in case of the scale is not given...
ExportResult exportToImage(const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings)
Exports the layout to the filePath, using the specified export settings.
SERVER_EXPORT QString getServerFid(const QgsFeature &feature, const QgsAttributeList &pkAttributes)
Returns the feature id based on primary keys.
void render(const QgsMapSettings &mapSettings, QImage *image)
Sequential or parallel map rendering.
QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
int wmsPrecisionAsInt() const
Returns WMS_PRECISION parameter as an int or its default value if not defined.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
QList< QgsWmsParametersLayer > mLayers
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
QList< QgsFeatureStore > QgsFeatureStoreList
QColor selectionColor() const
Gets color that is used for drawing of selected vector features.
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:571
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer&#39;s style manager.
double y
Definition: qgspointxy.h:48
Counts the features in a QgsVectorLayer in task.
void setSize(double size)
Sets the size of the buffer.
A class to represent a 2D point.
Definition: qgspointxy.h:43
QgsEditorWidgetSetup editorWidgetSetup(int index) const
The editor widget setup defines which QgsFieldFormatter and editor widget will be used for the field ...
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:341
void setFont(const QFont &font)
Sets the font used for rendering text.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
SERVER_EXPORT QString wmsFeatureInfoDocumentElement(const QgsProject &project)
Returns the document element name for XML GetFeatureInfo request.
double length() const
Returns the length of the measurement.
void layoutObjects(QList< T * > &objectList) const
Returns a list of layout objects (items and multiframes) of a specific type.
Definition: qgslayout.h:140
void setOutputDpi(double dpi)
Sets DPI used for conversion between real world units (e.g. mm) and pixels.
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QString title() const
Returns the title of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:272
QString j() const
Returns J parameter or an empty string if not defined.
QgsRenderer(QgsServerInterface *serverIface, const QgsProject *project, QgsWmsParameters &parameters)
Constructor.
X-coordinate data defined label position.
Container of fields for a vector layer.
Definition: qgsfields.h:42
SERVER_EXPORT QHash< QString, QString > wmsFeatureInfoLayerAliasMap(const QgsProject &project)
Returns the mapping between layer name and wms layer name for GetFeatureInfo request.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
void selectByIds(const QgsFeatureIds &ids, SelectBehavior behavior=SetSelection)
Select matching features using a list of feature IDs.
SERVER_EXPORT bool wmsInfoFormatSia2045(const QgsProject &project)
Returns if the info format is SIA20145.
bool setAttribute(int field, const QVariant &attr)
Set an attribute&#39;s value by field index.
Definition: qgsfeature.cpp:211
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group.
Format
Output format for the response.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
static QDomElement rectangleToGMLBox(QgsRectangle *box, QDomDocument &doc, int precision=17)
Exports the rectangle to GML2 Box.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer&#39;s CRS
void setSymbologyExport(QgsDxfExport::SymbologyExport e)
Set symbology export mode.
Definition: qgsdxfexport.h:176
bool withGeometry() const
Returns if the client wants the feature info response with geometry information.
void setMapUnitsPerPixel(double mapUnitsPerPixel)
Sets the mMmPerMapUnit calculated by mapUnitsPerPixel mostly taken from the map settings.
QStringList filters() const
Returns the list of filters found in FILTER parameter.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
Exception thrown in case of malformed request.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer&#39;s CRS to destination CRS.
bool ruleLabelAsBool() const
Returns RULELABEL as a bool.
Layers and optional attribute index to split into multiple layers using attribute value as layer name...
Definition: qgsdxfexport.h:59
const QgsCoordinateReferenceSystem & crs
RAII class to restore layer configuration on destruction (opacity, filters, ...)
QString crs() const
Returns CRS or an empty string if none is defined.
QgsFields fields
Definition: qgsfeature.h:66
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
QVariantMap config() const
bool withGeom
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:356
void setExtent(const QgsRectangle &extent)
When rendering a map layer, calling this method sets the "clipping" extent for the layer (in the laye...
bool layerTitleAsBool() const
Returns LAYERTITLE as a bool or its default value if not defined.
QMap< int, QVariant > results() const
Returns the identify results.
QString dpi() const
Returns DPI parameter or an empty string if not defined.
QList< int > pages
List of specific pages to export, or an empty list to export all pages.
int xAsInt() const
Returns X parameter as an int or its default value if not defined.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
QgsLayerTreeLayer * addLayer(QgsMapLayer *layer)
Append a new layer node for given map layer.
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
Abstract base class for annotation items which are drawn over a map.
Definition: qgsannotation.h:48
QString bbox() const
Returns BBOX if defined or an empty string.
The QgsMapSettings class contains configuration for rendering of the map.
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:121
SERVER_EXPORT bool wmsFeatureInfoAddWktGeometry(const QgsProject &project)
Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request.
Raster identify results container.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QList< QgsMapLayer * > layers() const
Gets list of layers for map rendering The layers are stored in the reverse order of how they are rend...
void dump() const
Dumps parameters.
QImage * getMap(HitTest *hitTest=nullptr)
Returns the map as an image (or a null pointer in case of error).
static void applyAccessControlLayerFilters(const QgsAccessControl *accessControl, QgsMapLayer *mapLayer, QHash< QgsMapLayer *, QString > &originalLayerFilters)
Apply filter from AccessControl.
QList< QgsWmsParametersHighlightLayer > highlightLayersParameters() const
Returns parameters for each highlight layer.
Format format() const
Returns format.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QHash< QgsVectorLayer *, SymbolSet > HitTest
QSize imageSize
Manual size in pixels for output image.
bool withMapTip() const
withMapTip
const QgsLayoutManager * layoutManager() const
Returns the project&#39;s layout manager, which manages compositions within the project.
bool transparentAsBool() const
Returns TRANSPARENT parameter as a bool or its default value if not defined.
QgsRasterDataProvider * dataProvider() override
Returns the layer&#39;s data provider.
Property
Data definable properties.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Format infoFormat() const
Returns infoFormat.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QString srcHeight() const
Returns SRCHEIGHT parameter or an empty string if not defined.
Layout graphical items for displaying a map.
void setOutputSize(QSize size)
Sets the size of the resulting map image.
QByteArray getPrint(const QString &formatString)
Returns printed page as binary.
QgsDxfExport getDxf(const QMap< QString, QString > &options)
Returns the map as DXF data.
bool displayAll
If true, all features will be labelled even when overlaps occur.
void setSize(double size)
Sets the size for rendered text.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
#define STRING_TO_FID(str)
Definition: qgsfeatureid.h:31
bool showFeatureCountAsBool() const
Returns SHOWFEATURECOUNT as a bool.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
QgsRasterRenderer * renderer() const
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:32
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
QSize outputSize() const
Returns the size of the resulting map image.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:161
SERVER_EXPORT QString wmsFeatureInfoDocumentElementNs(const QgsProject &project)
Returns the document element namespace for XML GetFeatureInfo request.
virtual QgsRectangle extent() const
Returns the extent of the layer.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
int heightAsInt() const
Returns HEIGHT parameter as an int or its default value if not defined.
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout&#39;s page collection, which stores and manages page items in the layout...
Definition: qgslayout.cpp:456
const QString & typeName
A class to describe the version of a project.
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
Definition: qgsfeature.cpp:202
Provides an interface to retrieve and manipulate WMS parameters received from the client...
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:238
QImage * getLegendGraphics()
Returns the map legend as an image (or a null pointer in case of error).
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setMapScale(double scale)
Sets the legend map scale.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is a null pointer, the node is a root node. ...
virtual QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const
Draws symbol on the left side of the item.
QString request() const
Returns REQUEST parameter as a string or an empty string if not defined.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
bool rasterizeWholeImage
Set to true to force whole layout to be rasterized while exporting.
void setY(double y)
Sets the y value of the point.
Definition: qgspointxy.h:113
The QgsLegendSettings class stores the appearance and layout settings for legend drawing with QgsLege...
SERVER_EXPORT bool wmsFeatureInfoSegmentizeWktGeometry(const QgsProject &project)
Returns if the geometry has to be segmentize in GetFeatureInfo request.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
QStringList layerIds() const
Gets list of layer IDs for map rendering The layers are stored in the reverse order of how they are r...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setFilter(const QgsVectorLayer *layer, const QgsExpression &expression)
Set a filter for the given layer.
QString filterGeom() const
Returns the filter geometry found in FILTER_GEOM parameter.
Horizontal alignment for data defined label position (Left, Center, Right)
void clear()
Clear any information from this layer tree.
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
void setColor(const QColor &color)
Sets the color that text will be rendered in.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void setSelectionColor(const QColor &color)
Sets color that is used for drawing of selected vector features.
QList< QgsWmsParametersHighlightLayer > mHighlightLayers
int infoFormatVersion() const
Returns the infoFormat version for GML.
int polygonToleranceAsInt() const
Returns FI_POLYGON_TOLERANCE parameter as an integer.
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
void removeMultiFrame(QgsLayoutMultiFrame *multiFrame)
Removes a multiFrame from the layout (but does not delete it).
Definition: qgslayout.cpp:577
QString subsetString
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
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.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false) ...
Definition: qgsfields.cpp:59
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
Reads and writes project states.
Definition: qgsproject.h:89
void setLegendFilterByScale(double scale)
Force only display of legend nodes which are valid for a given scale.
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
double mapUnitsPerPixel() const
Returns current map units per pixel.
QgsFeatureRenderer * renderer()
Returns renderer.
int srcWidthAsInt() const
Returns SRCWIDTH parameter as an int or its default value if not defined.
ExportResult exportToSvg(const QString &filePath, const QgsLayoutExporter::SvgExportSettings &settings)
Exports the layout as an SVG to the filePath, using the specified export settings.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
QString pointTolerance() const
Returns FI_POINT_TOLERANCE parameter or an empty string if not defined.
double dpiAsDouble() const
Returns DPI parameter as an int or its default value if not defined.
QString lineTolerance() const
Returns FI_LINE_TOLERANCE parameter or an empty string if not defined.
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
Abstract base class for all geometries.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
QList< QgsWmsParametersLayer > layersParameters() const
Returns parameters for each layer found in LAYER/LAYERS.
Manages storage of a set of layouts.
Keeps the number of features and export symbology per feature (using the first symbol level) ...
Definition: qgsdxfexport.h:83
virtual bool readSld(const QDomNode &node, QString &errorMessage)
Definition: qgsmaplayer.h:832
QSizeF minimumSize()
Run the layout algorithm and determine the size required for legend.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
QString layoutParameter(const QString &id, bool &ok) const
Returns a layout parameter thanks to its id.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else...
QgsAnnotationManager * annotationManager()
Returns pointer to the project&#39;s annotation manager.
void setX(double x)
Sets the x value of the point.
Definition: qgspointxy.h:104
QgsCoordinateTransformContext transformContext() const
Returns the transform context, for use when a destinationCrs() has been set and reprojection is requi...
double x
Definition: qgspointxy.h:47
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
Exception base class for server exceptions.
void setName(const QString &n) override
Sets the layer&#39;s name.
A field formatter helps to handle and display values for a field.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
int featureCountAsInt() const
Returns FEATURE_COUNT as an integer.
Median cut implementation.
void refreshLayerLegend(QgsLayerTreeLayer *nodeLayer)
Force a refresh of legend nodes of a layer node.
QStringList allLayersNickname() const
Returns nickname of layers found in LAYER and LAYERS parameters.
virtual int capabilities() const
Returns a bitmask containing the supported capabilities.
QgsExpressionContext & expressionContext()
Gets the expression context.
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
Handles rendering and exports of layouts to various formats.
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
unsigned int placementFlags
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle...
Definition: qgsrectangle.h:358
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
Placement
Placement modes which determine how label candidates are generated for a feature. ...
QSet< QString > SymbolSet
int jAsInt() const
Returns J parameter as an int or its default value if not defined.
Contains settings relating to exporting layouts to PDF.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setSymbologyScale(double scale)
Set reference scale for output.
Definition: qgsdxfexport.h:142
QString what() const
Definition: qgsexception.h:48
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins...
Special style, item is hidden including margins around.
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
Contains information about the context of a rendering operation.
Contains settings relating to exporting layouts to raster images.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object...
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
QString asWkt(int precision=17) const
Exports the geometry to WKT.
int srcHeightAsInt() const
Returns SRCHEIGHT parameter as an int or its default value if not defined.
QSet< QString > excludeAttributesWms() const
A set of attributes that are not advertised in WMS requests with QGIS server.
Transform from destination to source CRS.
QString i() const
Returns I parameter or an empty string if not defined.
void removeLayoutItem(QgsLayoutItem *item)
Removes an item from the layout.
Definition: qgslayout.cpp:552
void setLayerTitleAsName(bool layerTitleAsName)
Enable use of title (where set) instead of layer name, when attribute index of corresponding layer in...
Definition: qgsdxfexport.h:204
QList< QgsMapLayer * > layers() const
Returns the stored layer set.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void setSelectionColor(const QColor &color)
Sets color that is used for drawing of selected vector features.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns project&#39;s global labeling engine settings.
QPointF point
Top-left corner of the legend item.
void setExtent(const QgsRectangle &r)
Set extent of area to export.
Definition: qgsdxfexport.h:189
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
Holder for the widget type and its configuration for a field.
QString mapTipTemplate
static bool isCurvedType(Type type)
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:744
SERVER_EXPORT int wmsFeatureInfoPrecision(const QgsProject &project)
Returns the geometry precision for GetFeatureInfo request.
static QgsFeatureRenderer * loadSld(const QDomNode &node, QgsWkbTypes::GeometryType geomType, QString &errorMessage)
Create a new renderer according to the information contained in the UserStyle element of a SLD style ...
QStringList findLayerIds() const
Find layer IDs used in all layer nodes.
Container for settings relating to a text buffer.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
ExportResult exportToPdf(const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings)
Exports the layout as a PDF to the filePath, using the specified export settings. ...
Exception thrown when data access violates access controls.
Proxy for sequential or parallel map render job.
double dist
Distance from feature to the label.
QString externalWMSUri(const QString &id) const
Returns the external WMS uri.
double scaleAsDouble() const
Returns SCALE as a double.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
This class represents a coordinate reference system (CRS).
QgsFeatureFilterProviderGroup & addProvider(const QgsFeatureFilterProvider *provider)
Add another filter provider to the group.
virtual QgsRasterIdentifyResult identify(const QgsPointXY &point, QgsRaster::IdentifyFormat format, const QgsRectangle &boundingBox=QgsRectangle(), int width=0, int height=0, int dpi=96)
Identify raster value(s) found on the point position.
QgsMasterLayoutInterface * layoutByName(const QString &name) const
Returns the layout with a matching name, or nullptr if no matching layouts were found.
static QDomElement geometryToGML(const QgsGeometry &geometry, QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, const QString &srsName, bool invertAxisOrientation, const QString &gmlIdBase, int precision=17)
Exports the geometry to GML.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer&#39;s CRS to output CRS
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
QString authid() const
Returns the authority identifier for the CRS.
Class for doing transforms between two map coordinate systems.
static QString displayString(Type type)
Returns a display string type for a WKB type, e.g., the geometry name used in WKT geometry representa...
virtual bool setSubsetString(const QString &subset)
Set the string (typically sql) used to define a subset of the layer.
static QDomElement rectangleToGMLEnvelope(QgsRectangle *env, QDomDocument &doc, int precision=17)
Exports the rectangle to GML3 Envelope.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
QString bandName(int bandNoInt) const
Returns the name of a band given its number.
const QgsCoordinateReferenceSystem & outputCrs
int pointToleranceAsInt() const
Returns FI_POINT_TOLERANCE parameter as an integer.
int yAsInt() const
Returns Y parameter as an int or its default value if not defined.
const QgsMapToPixel & mapToPixel() const
Returns the context&#39;s map to pixel transform, which transforms between map coordinates and device coo...
Basic implementation of the labeling interface.
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...
QgsLegendSettings legendSettings() const
Returns legend settings.
QString id() const
Returns the item&#39;s ID name.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text&#39;s buffer settings.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
double scale() const
Returns the map scale.
QString name
Definition: qgsmaplayer.h:67
Enable vector simplification and other rendering optimizations.
QgsRectangle bboxAsRectangle() const
Returns BBOX as a rectangle if defined and valid.
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
void setOpacity(double opacity)
Sets the opacity for the renderer, where opacity is a value between 0 (totally transparent) and 1...
QgsGeometry geometry
Definition: qgsfeature.h:67
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QString polygonTolerance() const
Returns FI_POLYGON_TOLERANCE parameter or an empty string if not defined.
SERVER_EXPORT QString wmsFeatureInfoSchema(const QgsProject &project)
Returns the schema URL for XML GetFeatureInfo request.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project&#39;s layer tree.
Print layout, a QgsLayout subclass for static or atlas-based layouts.
QString srcWidth() const
Returns SRCWIDTH parameter or an empty string if not defined.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
A layout item subclass for map legends.
Y-coordinate data defined label position.
bool nextFeature(QgsFeature &f)
SERVER_EXPORT QStringList wmsRestrictedLayers(const QgsProject &project)
Returns the restricted layer name list.
Container of other groups and layers.
Container for all settings relating to text rendering.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
SERVER_EXPORT QString wmsRootName(const QgsProject &project)
Returns the WMS root layer name defined in a QGIS project.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
A vector of attributes.
Definition: qgsattributes.h:57
QString y() const
Returns Y parameter or an empty string if not defined.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
bool parallelRendering() const
Returns parallel rendering setting.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
int iAsInt() const
Returns I parameter as an int or its default value if not defined.
Exception class for WMS service exceptions (for compatibility only).
Represents a vector layer which manages a vector based data sets.
double labelXOffset
offset from the left side where label should start
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
Contains settings relating to exporting layouts to SVG.
QString x() const
Returns X parameter or an empty string if not defined.
Base class for frame items, which form a layout multiframe item.
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
QgsProjectVersion versionAsNumber() const
Returns VERSION parameter if defined or its default value.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
A layout multiframe subclass for HTML content.
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top) ...
Defines a QGIS exception class.
Definition: qgsexception.h:34
WMS parameter received from the client.
int getImageQuality() const
Returns the image quality to use for getMap request.
QMap< QString, QgsMapLayer * > mapLayers() const
Returns a map of all registered layers by layer ID.
void addLayers(const QList< QgsDxfExport::DxfLayer > &layers)
Add layers to export.
void setColor(const QColor &color)
Sets the color for the buffer.
int priority
Label priority.
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:130
QgsWmsParametersComposerMap composerMapParameters(int mapId) const
Returns the requested parameters for a composer map parameter.
Raster renderer pipe that applies colors to a raster.
QString scale() const
Returns SCALE parameter or an empty string if none is defined.
QString imageQuality() const
Returns IMAGE_QUALITY parameter or an empty string if not defined.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
QgsAttributes attributes
Definition: qgsfeature.h:65
A filter filter provider grouping several filter providers.
bool setCurrentStyle(const QString &name)
Set a different style as the current style - will apply it to the layer.
QString height() const
Returns HEIGHT parameter or an empty string if not defined.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:70
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
QString rule() const
Returns RULE parameter or an empty string if none is defined.
QString width() const
Returns WIDTH parameter or an empty string if not defined.
bool infoFormatIsImage() const
Checks if INFO_FORMAT parameter is one of the image formats (PNG, JPG).
SERVER_EXPORT int wmsImageQuality(const QgsProject &project)
Returns the quality for WMS images defined in a QGIS project.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208
void invert()
Swap x/y coordinates in the rectangle.
Definition: qgsrectangle.h:531
int imageQualityAsInt() const
Returns IMAGE_QUALITY parameter as an integer.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label&#39;s property collection, used for data defined overrides.
QString composerTemplate() const
Returns TEMPLATE parameter or an empty string if not defined.
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.
The QgsLegendRenderer class handles automatic layout and rendering of legend.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
int lineToleranceAsInt() const
Returns FI_LINE_TOLERANCE parameter as an integer.
SERVER_EXPORT int wmsMaxHeight(const QgsProject &project)
Returns the maximum height for WMS images defined in a QGIS project.
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
void setLayers(const QList< QgsMapLayer * > &layers)
Set list of layers for map rendering.
QColor backgroundColorAsColor() const
Returns BGCOLOR parameter as a QColor or its default value if not defined.
QString fieldName
Name of field (or an expression) to use for label text.
SERVER_EXPORT bool wmsUseLayerIds(const QgsProject &project)
Returns if layer ids are used as name in WMS.
Exports one feature per symbol layer (considering symbol levels)
Definition: qgsdxfexport.h:84
int maxThreads() const
Returns the maximum number of threads to use.
void setOpacity(double opacity)
Sets the opacity for the vector layer, where opacity is a value between 0 (totally transparent) and 1...