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