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