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 ¶meters )
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 ¶ms : 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 ¶m : 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