1 /***************************************************************************
2                               qgswmsrendercontext.cpp
3                               ---------------------
4   begin                : March 22, 2019
5   copyright            : (C) 2019 by Paul Blottiere
6   email                : paul.blottiere@oslandia.com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgslayertree.h"
19 
20 #include "qgsrasterlayer.h"
21 #include "qgswmsrendercontext.h"
22 #include "qgswmsserviceexception.h"
23 #include "qgsserverprojectutils.h"
24 
25 using namespace QgsWms;
26 
27 const double OGC_PX_M = 0.00028; // OGC reference pixel size in meter
QgsWmsRenderContext(const QgsProject * project,QgsServerInterface * interface)28 QgsWmsRenderContext::QgsWmsRenderContext( const QgsProject *project, QgsServerInterface *interface )
29   : mProject( project )
30   , mInterface( interface )
31   , mFlags()
32 {
33 }
34 
~QgsWmsRenderContext()35 QgsWmsRenderContext::~QgsWmsRenderContext()
36 {
37   qDeleteAll( mExternalLayers );
38   mExternalLayers.clear();
39 }
40 
setParameters(const QgsWmsParameters & parameters)41 void QgsWmsRenderContext::setParameters( const QgsWmsParameters &parameters )
42 {
43   mParameters = parameters;
44 
45   initRestrictedLayers();
46   initNicknameLayers();
47 
48   searchLayersToRender();
49   removeUnwantedLayers();
50   checkLayerReadPermissions();
51 
52   std::reverse( mLayersToRender.begin(), mLayersToRender.end() );
53 }
54 
setFlag(const Flag flag,const bool on)55 void QgsWmsRenderContext::setFlag( const Flag flag, const bool on )
56 {
57   if ( on )
58   {
59     mFlags |= flag;
60   }
61   else
62   {
63     mFlags &= ~flag;
64   }
65 }
66 
testFlag(Flag flag) const67 bool QgsWmsRenderContext::testFlag( Flag flag ) const
68 {
69   return mFlags.testFlag( flag );
70 }
71 
parameters() const72 QgsWmsParameters QgsWmsRenderContext::parameters() const
73 {
74   return mParameters;
75 }
76 
settings() const77 const QgsServerSettings &QgsWmsRenderContext::settings() const
78 {
79   return *mInterface->serverSettings();
80 }
81 
project() const82 const QgsProject *QgsWmsRenderContext::project() const
83 {
84   return mProject;
85 }
86 
sld(const QgsMapLayer & layer) const87 QDomElement QgsWmsRenderContext::sld( const QgsMapLayer &layer ) const
88 {
89   QDomElement sld;
90 
91   const QString nickname = layerNickname( layer );
92   if ( mSlds.contains( nickname ) )
93   {
94     sld = mSlds[ nickname ];
95   }
96 
97   return sld;
98 }
99 
style(const QgsMapLayer & layer) const100 QString QgsWmsRenderContext::style( const QgsMapLayer &layer ) const
101 {
102   QString style;
103 
104   const QString nickname = layerNickname( layer );
105   if ( mStyles.contains( nickname ) )
106   {
107     style = mStyles[ nickname ];
108   }
109 
110   return style;
111 }
112 
parameters(const QgsMapLayer & layer) const113 QgsWmsParametersLayer QgsWmsRenderContext::parameters( const QgsMapLayer &layer ) const
114 {
115   QgsWmsParametersLayer parameters;
116 
117   for ( const auto &params : mParameters.layersParameters() )
118   {
119     if ( params.mNickname == layerNickname( layer ) )
120     {
121       parameters = params;
122       break;
123     }
124   }
125 
126   return parameters;
127 }
128 
imageQuality() const129 int QgsWmsRenderContext::imageQuality() const
130 {
131   int imageQuality = QgsServerProjectUtils::wmsImageQuality( *mProject );
132 
133   if ( !mParameters.imageQuality().isEmpty() )
134   {
135     imageQuality = mParameters.imageQualityAsInt();
136   }
137 
138   return imageQuality;
139 }
140 
tileBuffer() const141 int QgsWmsRenderContext::tileBuffer() const
142 {
143   int tileBuffer = 0;
144 
145   if ( mParameters.tiledAsBool() )
146   {
147     tileBuffer = QgsServerProjectUtils::wmsTileBuffer( *mProject );
148   }
149 
150   return tileBuffer;
151 }
152 
renderMapTiles() const153 bool QgsWmsRenderContext::renderMapTiles() const
154 {
155   return QgsServerProjectUtils::wmsRenderMapTiles( *mProject );
156 }
157 
precision() const158 int QgsWmsRenderContext::precision() const
159 {
160   int precision = QgsServerProjectUtils::wmsFeatureInfoPrecision( *mProject );
161 
162   if ( mParameters.wmsPrecisionAsInt() > -1 )
163   {
164     precision = mParameters.wmsPrecisionAsInt();
165   }
166 
167   return precision;
168 }
169 
dotsPerMm() const170 qreal QgsWmsRenderContext::dotsPerMm() const
171 {
172   // Apply DPI parameter if present. This is an extension of QGIS Server
173   // compared to WMS 1.3.
174   // Because of backwards compatibility, this parameter is optional
175   qreal dpm = 1 / OGC_PX_M;
176 
177   if ( !mParameters.dpi().isEmpty() )
178   {
179     dpm = mParameters.dpiAsDouble() / 0.0254;
180   }
181 
182   return dpm / 1000.0;
183 }
184 
flattenedQueryLayers(const QStringList & layerNames) const185 QStringList QgsWmsRenderContext::flattenedQueryLayers( const QStringList &layerNames ) const
186 {
187   QStringList result;
188   std::function <QStringList( const QString &name )> findLeaves = [ & ]( const QString & name ) -> QStringList
189   {
190     QStringList _result;
191     if ( mLayerGroups.contains( name ) )
192     {
193       const auto &layers  { mLayerGroups[ name ] };
194       for ( const auto &l : layers )
195       {
196         const auto nick { layerNickname( *l ) };
197         // This handles the case for root (fake) group
198         if ( mLayerGroups.contains( nick ) )
199         {
200           _result.append( name );
201         }
202         else
203         {
204           _result.append( findLeaves( nick ) );
205         }
206       }
207     }
208     else
209     {
210       _result.append( name );
211     }
212     return _result;
213   };
214 
215   for ( const auto &name : std::as_const( layerNames ) )
216   {
217     result.append( findLeaves( name ) );
218   }
219   return result;
220 }
221 
layersToRender() const222 QList<QgsMapLayer *> QgsWmsRenderContext::layersToRender() const
223 {
224   return mLayersToRender;
225 }
226 
layers() const227 QList<QgsMapLayer *> QgsWmsRenderContext::layers() const
228 {
229   return mNicknameLayers.values();
230 }
231 
scaleDenominator() const232 double QgsWmsRenderContext::scaleDenominator() const
233 {
234   double denominator = -1;
235 
236   if ( mScaleDenominator >= 0 )
237   {
238     denominator = mScaleDenominator;
239   }
240   else if ( mFlags & UseScaleDenominator && ! mParameters.scale().isEmpty() )
241   {
242     denominator = mParameters.scaleAsDouble();
243   }
244 
245   return denominator;
246 }
247 
setScaleDenominator(double scaleDenominator)248 void QgsWmsRenderContext::setScaleDenominator( double scaleDenominator )
249 {
250   mScaleDenominator = scaleDenominator;
251   removeUnwantedLayers();
252 }
253 
updateExtent() const254 bool QgsWmsRenderContext::updateExtent() const
255 {
256   bool update = false;
257 
258   if ( mFlags & UpdateExtent && ! mParameters.bbox().isEmpty() )
259   {
260     update = true;
261   }
262 
263   return update;
264 }
265 
layerNickname(const QgsMapLayer & layer) const266 QString QgsWmsRenderContext::layerNickname( const QgsMapLayer &layer ) const
267 {
268   QString name = layer.shortName();
269   if ( QgsServerProjectUtils::wmsUseLayerIds( *mProject ) )
270   {
271     name = layer.id();
272   }
273   else if ( name.isEmpty() )
274   {
275     name = layer.name();
276   }
277 
278   return name;
279 }
280 
layer(const QString & nickname) const281 QgsMapLayer *QgsWmsRenderContext::layer( const QString &nickname ) const
282 {
283   QgsMapLayer *mlayer = nullptr;
284 
285   for ( auto layer : mLayersToRender )
286   {
287     if ( layerNickname( *layer ).compare( nickname ) == 0 )
288     {
289       mlayer = layer;
290       break;
291     }
292   }
293 
294   return mlayer;
295 }
296 
isValidLayer(const QString & nickname) const297 bool QgsWmsRenderContext::isValidLayer( const QString &nickname ) const
298 {
299   return layer( nickname ) != nullptr;
300 }
301 
layersFromGroup(const QString & nickname) const302 QList<QgsMapLayer *> QgsWmsRenderContext::layersFromGroup( const QString &nickname ) const
303 {
304   return mLayerGroups.value( nickname );
305 }
306 
isValidGroup(const QString & name) const307 bool QgsWmsRenderContext::isValidGroup( const QString &name ) const
308 {
309   return mLayerGroups.contains( name );
310 }
311 
initNicknameLayers()312 void QgsWmsRenderContext::initNicknameLayers()
313 {
314   for ( QgsMapLayer *ml : mProject->mapLayers() )
315   {
316     mNicknameLayers.insert( layerNickname( *ml ), ml );
317   }
318 
319   // init groups
320   const QString rootName { QgsServerProjectUtils::wmsRootName( *mProject ) };
321   const QgsLayerTreeGroup *root = mProject->layerTreeRoot();
322 
323   initLayerGroupsRecursive( root, rootName.isEmpty() ? mProject->title() : rootName );
324 }
325 
initLayerGroupsRecursive(const QgsLayerTreeGroup * group,const QString & groupName)326 void QgsWmsRenderContext::initLayerGroupsRecursive( const QgsLayerTreeGroup *group, const QString &groupName )
327 {
328   if ( !groupName.isEmpty() )
329   {
330     mLayerGroups[groupName] = QList<QgsMapLayer *>();
331     const auto projectLayerTreeRoot { mProject->layerTreeRoot() };
332     const auto treeGroupLayers { group->findLayers() };
333     // Fast track if there is no custom layer order,
334     // otherwise reorder layers.
335     if ( ! projectLayerTreeRoot->hasCustomLayerOrder() )
336     {
337       for ( const auto &tl : treeGroupLayers )
338       {
339         mLayerGroups[groupName].push_back( tl->layer() );
340       }
341     }
342     else
343     {
344       const auto projectLayerOrder { projectLayerTreeRoot->layerOrder() };
345       // Flat list containing the layers from the tree nodes
346       QList<QgsMapLayer *> groupLayersList;
347       for ( const auto &tl : treeGroupLayers )
348       {
349         groupLayersList << tl->layer();
350       }
351       for ( const auto &l : projectLayerOrder )
352       {
353         if ( groupLayersList.contains( l ) )
354         {
355           mLayerGroups[groupName].push_back( l );
356         }
357       }
358     }
359   }
360 
361   for ( const QgsLayerTreeNode *child : group->children() )
362   {
363     if ( child->nodeType() == QgsLayerTreeNode::NodeGroup )
364     {
365       QString name = child->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
366 
367       if ( name.isEmpty() )
368         name = child->name();
369 
370       initLayerGroupsRecursive( static_cast<const QgsLayerTreeGroup *>( child ), name );
371 
372     }
373   }
374 }
375 
initRestrictedLayers()376 void QgsWmsRenderContext::initRestrictedLayers()
377 {
378   mRestrictedLayers.clear();
379 
380   // get name of restricted layers/groups in project
381   const QStringList restricted = QgsServerProjectUtils::wmsRestrictedLayers( *mProject );
382 
383   // extract restricted layers from excluded groups
384   QStringList restrictedLayersNames;
385   QgsLayerTreeGroup *root = mProject->layerTreeRoot();
386 
387   for ( const QString &l : std::as_const( restricted ) )
388   {
389     const QgsLayerTreeGroup *group = root->findGroup( l );
390     if ( group )
391     {
392       const QList<QgsLayerTreeLayer *> groupLayers = group->findLayers();
393       for ( QgsLayerTreeLayer *treeLayer : groupLayers )
394       {
395         restrictedLayersNames.append( treeLayer->name() );
396       }
397     }
398     else
399     {
400       restrictedLayersNames.append( l );
401     }
402   }
403 
404   // build output with names, ids or short name according to the configuration
405   const QList<QgsLayerTreeLayer *> layers = root->findLayers();
406   for ( QgsLayerTreeLayer *layer : layers )
407   {
408     if ( restrictedLayersNames.contains( layer->name() ) )
409     {
410       mRestrictedLayers.append( layerNickname( *layer->layer() ) );
411     }
412   }
413 }
414 
searchLayersToRender()415 void QgsWmsRenderContext::searchLayersToRender()
416 {
417   mLayersToRender.clear();
418   mStyles.clear();
419   mSlds.clear();
420 
421   if ( ! mParameters.sldBody().isEmpty() )
422   {
423     searchLayersToRenderSld();
424   }
425   else
426   {
427     searchLayersToRenderStyle();
428   }
429 
430   if ( mFlags & AddQueryLayers )
431   {
432     const QStringList queryLayerNames = flattenedQueryLayers( mParameters.queryLayersNickname() );
433     for ( const QString &layerName : queryLayerNames )
434     {
435       const QList<QgsMapLayer *> layers = mNicknameLayers.values( layerName );
436       for ( QgsMapLayer *lyr : layers )
437         if ( !mLayersToRender.contains( lyr ) )
438         {
439           mLayersToRender.append( lyr );
440         }
441     }
442   }
443 
444   if ( mFlags & AddAllLayers )
445   {
446     const QStringList queryLayerNames = flattenedQueryLayers( mParameters.allLayersNickname() );
447     for ( const QString &layerName : queryLayerNames )
448     {
449       const QList<QgsMapLayer *> layers = mNicknameLayers.values( layerName );
450       for ( QgsMapLayer *lyr : layers )
451         if ( !mLayersToRender.contains( lyr ) )
452         {
453           mLayersToRender.append( lyr );
454         }
455     }
456   }
457 }
458 
searchLayersToRenderSld()459 void QgsWmsRenderContext::searchLayersToRenderSld()
460 {
461   const QString sld = mParameters.sldBody();
462 
463   if ( sld.isEmpty() )
464   {
465     return;
466   }
467 
468   QDomDocument doc;
469   ( void )doc.setContent( sld, true );
470   QDomElement docEl = doc.documentElement();
471 
472   QDomElement root = doc.firstChildElement( "StyledLayerDescriptor" );
473   QDomElement namedElem = root.firstChildElement( "NamedLayer" );
474 
475   if ( docEl.isNull() )
476   {
477     return;
478   }
479 
480   QDomNodeList named = docEl.elementsByTagName( "NamedLayer" );
481   for ( int i = 0; i < named.size(); ++i )
482   {
483     QDomNodeList names = named.item( i ).toElement().elementsByTagName( "Name" );
484     if ( !names.isEmpty() )
485     {
486       QString lname = names.item( 0 ).toElement().text();
487       if ( mNicknameLayers.contains( lname ) )
488       {
489         mSlds[lname] = namedElem;
490         mLayersToRender.append( mNicknameLayers.values( lname ) );
491       }
492       else if ( mLayerGroups.contains( lname ) )
493       {
494         for ( QgsMapLayer *layer : mLayerGroups[lname] )
495         {
496           const QString name = layerNickname( *layer );
497           mSlds[name] = namedElem;
498           mLayersToRender.insert( 0, layer );
499         }
500       }
501       else
502       {
503         QgsWmsParameter param( QgsWmsParameter::LAYER );
504         param.mValue = lname;
505         throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined,
506                                       param );
507       }
508     }
509   }
510 }
511 
searchLayersToRenderStyle()512 void QgsWmsRenderContext::searchLayersToRenderStyle()
513 {
514   for ( const QgsWmsParametersLayer &param : mParameters.layersParameters() )
515   {
516     const QString nickname = param.mNickname;
517     const QString style = param.mStyle;
518 
519     if ( ! param.mExternalUri.isEmpty() && ( mFlags & AddExternalLayers ) )
520     {
521       std::unique_ptr<QgsMapLayer> layer = std::make_unique< QgsRasterLayer >( param.mExternalUri, param.mNickname, QStringLiteral( "wms" ) );
522 
523       if ( layer->isValid() )
524       {
525         // to delete later
526         mExternalLayers.append( layer.release() );
527         mLayersToRender.append( mExternalLayers.last() );
528       }
529     }
530     else if ( mNicknameLayers.contains( nickname ) )
531     {
532       if ( !style.isEmpty() )
533       {
534         mStyles[nickname] = style;
535       }
536 
537       mLayersToRender.append( mNicknameLayers.values( nickname ) );
538     }
539     else if ( mLayerGroups.contains( nickname ) )
540     {
541       // Reverse order of layers from a group
542       QList<QString> layersFromGroup;
543       for ( QgsMapLayer *layer : mLayerGroups[nickname] )
544       {
545         const QString nickname = layerNickname( *layer );
546         if ( !style.isEmpty() )
547         {
548           mStyles[ nickname ] = style;
549         }
550         layersFromGroup.push_front( nickname );
551       }
552 
553       for ( const auto &name : layersFromGroup )
554       {
555         mLayersToRender.append( mNicknameLayers.values( name ) );
556       }
557     }
558     else
559     {
560       QgsWmsParameter param( QgsWmsParameter::LAYER );
561       param.mValue = nickname;
562       throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined,
563                                     param );
564     }
565   }
566 }
567 
layerScaleVisibility(const QString & name) const568 bool QgsWmsRenderContext::layerScaleVisibility( const QString &name ) const
569 {
570   bool visible = false;
571 
572   if ( ! mNicknameLayers.contains( name ) )
573   {
574     return visible;
575   }
576 
577   const QList<QgsMapLayer *>layers = mNicknameLayers.values( name );
578   for ( QgsMapLayer *layer : layers )
579   {
580     bool scaleBasedVisibility = layer->hasScaleBasedVisibility();
581     bool useScaleConstraint = ( scaleDenominator() > 0 && scaleBasedVisibility );
582 
583     if ( !useScaleConstraint || layer->isInScaleRange( scaleDenominator() ) )
584     {
585       visible = true;
586     }
587   }
588 
589   return visible;
590 }
591 
layerGroups() const592 QMap<QString, QList<QgsMapLayer *> > QgsWmsRenderContext::layerGroups() const
593 {
594   return mLayerGroups;
595 }
596 
mapWidth() const597 int QgsWmsRenderContext::mapWidth() const
598 {
599   int width = mParameters.widthAsInt();
600 
601   // May use SRCWIDTH to define image map size
602   if ( ( mFlags & UseSrcWidthHeight ) && mParameters.srcWidthAsInt() > 0 )
603   {
604     width = mParameters.srcWidthAsInt();
605   }
606 
607   return width;
608 }
609 
mapHeight() const610 int QgsWmsRenderContext::mapHeight() const
611 {
612   int height = mParameters.heightAsInt();
613 
614   // May use SRCHEIGHT to define image map size
615   if ( ( mFlags & UseSrcWidthHeight ) && mParameters.srcHeightAsInt() > 0 )
616   {
617     height = mParameters.srcHeightAsInt();
618   }
619 
620   return height;
621 }
622 
isValidWidthHeight() const623 bool QgsWmsRenderContext::isValidWidthHeight() const
624 {
625   return isValidWidthHeight( mapWidth(), mapHeight() );
626 }
627 
isValidWidthHeight(int width,int height) const628 bool QgsWmsRenderContext::isValidWidthHeight( int width, int height ) const
629 {
630   //test if maxWidth / maxHeight are set in the project or as an env variable
631   //and WIDTH / HEIGHT parameter is in the range allowed range
632   //WIDTH
633   const int wmsMaxWidthProj = QgsServerProjectUtils::wmsMaxWidth( *mProject );
634   const int wmsMaxWidthEnv = settings().wmsMaxWidth();
635   int wmsMaxWidth;
636   if ( wmsMaxWidthEnv != -1 && wmsMaxWidthProj != -1 )
637   {
638     // both are set, so we take the more conservative one
639     wmsMaxWidth = std::min( wmsMaxWidthProj, wmsMaxWidthEnv );
640   }
641   else
642   {
643     // none or one are set, so we take the bigger one which is the one set or -1
644     wmsMaxWidth = std::max( wmsMaxWidthProj, wmsMaxWidthEnv );
645   }
646 
647   if ( wmsMaxWidth != -1 && width > wmsMaxWidth )
648   {
649     return false;
650   }
651 
652   //HEIGHT
653   const int wmsMaxHeightProj = QgsServerProjectUtils::wmsMaxHeight( *mProject );
654   const int wmsMaxHeightEnv = settings().wmsMaxHeight();
655   int wmsMaxHeight;
656   if ( wmsMaxHeightEnv != -1 && wmsMaxHeightProj != -1 )
657   {
658     // both are set, so we take the more conservative one
659     wmsMaxHeight = std::min( wmsMaxHeightProj, wmsMaxHeightEnv );
660   }
661   else
662   {
663     // none or one are set, so we take the bigger one which is the one set or -1
664     wmsMaxHeight = std::max( wmsMaxHeightProj, wmsMaxHeightEnv );
665   }
666 
667   if ( wmsMaxHeight != -1 && height > wmsMaxHeight )
668   {
669     return false;
670   }
671 
672   // Sanity check from internal QImage checks (see qimage.cpp)
673   // this is to report a meaningful error message in case of
674   // image creation failure and to differentiate it from out
675   // of memory conditions.
676 
677   // depth for now it cannot be anything other than 32, but I don't like
678   // to hardcode it: I hope we will support other depths in the future.
679   uint depth = 32;
680   switch ( mParameters.format() )
681   {
682     case QgsWmsParameters::Format::JPG:
683     case QgsWmsParameters::Format::PNG:
684     default:
685       depth = 32;
686   }
687 
688   const int bytes_per_line = ( ( width * depth + 31 ) >> 5 ) << 2; // bytes per scanline (must be multiple of 4)
689 
690   if ( std::numeric_limits<int>::max() / depth < static_cast<uint>( width )
691        || bytes_per_line <= 0
692        || height <= 0
693        || std::numeric_limits<int>::max() / static_cast<uint>( bytes_per_line ) < static_cast<uint>( height )
694        || std::numeric_limits<int>::max() / sizeof( uchar * ) < static_cast<uint>( height ) )
695   {
696     return false;
697   }
698 
699   return true;
700 }
701 
mapTileBuffer(const int mapWidth) const702 double QgsWmsRenderContext::mapTileBuffer( const int mapWidth ) const
703 {
704   double buffer;
705   if ( mFlags & UseTileBuffer )
706   {
707     const QgsRectangle extent = mParameters.bboxAsRectangle();
708     if ( !mParameters.bbox().isEmpty() && extent.isEmpty() )
709     {
710       throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
711                                     mParameters[QgsWmsParameter::BBOX] );
712     }
713     buffer = tileBuffer() * ( extent.width() / mapWidth );
714   }
715   else
716   {
717     buffer = 0;
718   }
719   return buffer;
720 }
721 
mapSize(const bool aspectRatio) const722 QSize QgsWmsRenderContext::mapSize( const bool aspectRatio ) const
723 {
724   int width = mapWidth();
725   int height = mapHeight();
726 
727   // Adapt width / height if the aspect ratio does not correspond with the BBOX.
728   // Required by WMS spec. 1.3.
729   if ( aspectRatio
730        && mParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
731   {
732     QgsRectangle extent = mParameters.bboxAsRectangle();
733     if ( !mParameters.bbox().isEmpty() && extent.isEmpty() )
734     {
735       throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
736                                     mParameters[QgsWmsParameter::BBOX] );
737     }
738 
739     QString crs = mParameters.crs();
740     if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
741     {
742       crs = QString( "EPSG:4326" );
743       extent.invert();
744     }
745 
746     QgsCoordinateReferenceSystem outputCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crs );
747     if ( outputCrs.hasAxisInverted() )
748     {
749       extent.invert();
750     }
751 
752     if ( !extent.isEmpty() && height > 0 && width > 0 )
753     {
754       const double mapRatio = extent.width() / extent.height();
755       const double imageRatio = static_cast<double>( width ) / static_cast<double>( height );
756       if ( !qgsDoubleNear( mapRatio, imageRatio, 0.0001 ) )
757       {
758         // inspired by MapServer, mapdraw.c L115
759         const double cellsize = ( extent.width() / static_cast<double>( width ) ) * 0.5 + ( extent.height() / static_cast<double>( height ) ) * 0.5;
760         width = extent.width() / cellsize;
761         height = extent.height() / cellsize;
762       }
763     }
764   }
765 
766   if ( width <= 0 )
767   {
768     throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
769                                   mParameters[QgsWmsParameter::WIDTH] );
770   }
771   else if ( height <= 0 )
772   {
773     throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue,
774                                   mParameters[QgsWmsParameter::HEIGHT] );
775   }
776 
777   return QSize( width, height );
778 }
779 
removeUnwantedLayers()780 void QgsWmsRenderContext::removeUnwantedLayers()
781 {
782   QList<QgsMapLayer *> layers;
783 
784   for ( QgsMapLayer *layer : mLayersToRender )
785   {
786     const QString nickname = layerNickname( *layer );
787 
788     if ( ! isExternalLayer( nickname ) )
789     {
790       if ( !layerScaleVisibility( nickname ) )
791         continue;
792 
793       if ( mRestrictedLayers.contains( nickname ) )
794         continue;
795 
796       if ( mFlags & UseWfsLayersOnly )
797       {
798         if ( layer->type() != QgsMapLayerType::VectorLayer )
799         {
800           continue;
801         }
802 
803         const QStringList wfsLayers = QgsServerProjectUtils::wfsLayerIds( *mProject );
804         if ( ! wfsLayers.contains( layer->id() ) )
805         {
806           continue;
807         }
808       }
809     }
810 
811     layers.append( layer );
812   }
813 
814   mLayersToRender = layers;
815 }
816 
isExternalLayer(const QString & name) const817 bool QgsWmsRenderContext::isExternalLayer( const QString &name ) const
818 {
819   for ( const auto &layer : mExternalLayers )
820   {
821     if ( layer->name().compare( name ) == 0 )
822       return true;
823   }
824 
825   return false;
826 }
827 
checkLayerReadPermissions()828 void QgsWmsRenderContext::checkLayerReadPermissions()
829 {
830 #ifdef HAVE_SERVER_PYTHON_PLUGINS
831   for ( const auto layer : mLayersToRender )
832   {
833     if ( !accessControl()->layerReadPermission( layer ) )
834     {
835       throw QgsSecurityException( QStringLiteral( "You are not allowed to access to the layer: %1" ).arg( layer->name() ) );
836     }
837   }
838 #endif
839 }
840 
841 #ifdef HAVE_SERVER_PYTHON_PLUGINS
accessControl() const842 QgsAccessControl *QgsWmsRenderContext::accessControl() const
843 {
844   return mInterface->accessControls();
845 }
846 #endif
847