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