QGIS API Documentation 4.3.0-Master (0cfde48c85b)
Loading...
Searching...
No Matches
qgswmsrendercontext.cpp
Go to the documentation of this file.
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 "qgswmsrendercontext.h"
19
20#include "qgslayertree.h"
21#include "qgslayertreegroup.h"
23#include "qgsrasterlayer.h"
25#include "qgswmslayerinfos.h"
27#include "qgswmsutils.h"
28
29#include <QString>
30
31using namespace Qt::StringLiterals;
32
33using namespace QgsWms;
34
35const double OGC_PX_M = 0.00028; // OGC reference pixel size in meter
37 : mProject( project )
38 , mInterface( interface )
39 , mFlags()
40{}
41
43{
44 qDeleteAll( mExternalLayers );
45 mExternalLayers.clear();
46}
47
49{
50 mParameters = parameters;
51
52 initRestrictedLayers();
53 initNicknameLayers();
54 searchLayersToRender();
55 removeUnwantedLayers();
56
57 std::reverse( mLayersToRender.begin(), mLayersToRender.end() );
58}
59
60bool QgsWmsRenderContext::addLayerToRender( QgsMapLayer *layer )
61{
62 const bool allowed = checkLayerReadPermissions( layer );
63 if ( allowed )
64 {
65 mLayersToRender.append( layer );
66 }
67 return allowed;
68}
69
70
71void QgsWmsRenderContext::setFlag( const Flag flag, const bool on )
72{
73 if ( on )
74 {
75 mFlags |= flag;
76 }
77 else
78 {
79 mFlags &= ~flag;
80 }
81}
82
84{
85 return mFlags.testFlag( flag );
86}
87
89{
90 return mParameters;
91}
92
94{
95 return *mInterface->serverSettings();
96}
97
99{
100 return mProject;
101}
102
103QDomElement QgsWmsRenderContext::sld( const QgsMapLayer &layer ) const
104{
105 QDomElement sld;
106
107 const QString nickname = layerNickname( layer );
108 if ( mSlds.contains( nickname ) )
109 {
110 sld = mSlds[nickname];
111 }
112
113 return sld;
114}
115
117{
118 QString style;
119
120 const QString nickname = layerNickname( layer );
121 if ( mStyles.contains( nickname ) )
122 {
123 style = mStyles[nickname];
124 }
125
126 return style;
127}
128
130{
132
133 const QList<QgsWmsParametersLayer> cLayerParams { mParameters.layersParameters() };
134
135 for ( const auto &params : std::as_const( cLayerParams ) )
136 {
137 if ( params.mNickname == layerNickname( layer ) )
138 {
139 parameters = params;
140 break;
141 }
142 }
143
144 return parameters;
145}
146
148{
150
151 if ( !mParameters.imageQuality().isEmpty() )
152 {
153 imageQuality = mParameters.imageQualityAsInt();
154 }
155
156 return imageQuality;
157}
158
160{
161 int tileBuffer = 0;
162
163 if ( mParameters.tiledAsBool() )
164 {
166 }
167
168 return tileBuffer;
169}
170
175
177{
179
180 if ( mParameters.wmsPrecisionAsInt() > -1 )
181 {
182 precision = mParameters.wmsPrecisionAsInt();
183 }
184
185 return precision;
186}
187
189{
190 // Apply DPI parameter if present. This is an extension of QGIS Server
191 // compared to WMS 1.3.
192 // Because of backwards compatibility, this parameter is optional
193 qreal dpm = 1 / OGC_PX_M;
194
195 if ( !mParameters.dpi().isEmpty() )
196 {
197 dpm = mParameters.dpiAsDouble() / 0.0254;
198 }
199
200 return dpm / 1000.0;
201}
202
203QStringList QgsWmsRenderContext::flattenedQueryLayers( const QStringList &layerNames ) const
204{
205 QStringList result;
206 std::function<QStringList( const QString &name )> findLeaves = [&]( const QString &name ) -> QStringList {
207 QStringList _result;
208 if ( mLayerGroups.contains( name ) )
209 {
210 const auto &layers { mLayerGroups[name] };
211 for ( const auto &l : layers )
212 {
213 // Only add allowed layers
214 if ( checkLayerReadPermissions( l ) )
215 {
216 const auto nick { layerNickname( *l ) };
217 // This handles the case for root (fake) group
218 if ( mLayerGroups.contains( nick ) )
219 {
220 _result.append( name );
221 }
222 else
223 {
224 _result.append( findLeaves( nick ) );
225 }
226 }
227 }
228 }
229 else
230 {
231 _result.append( name );
232 }
233 return _result;
234 };
235
236 for ( const auto &name : std::as_const( layerNames ) )
237 {
238 result.append( findLeaves( name ) );
239 }
240 return result;
241}
242
243QList<QgsMapLayer *> QgsWmsRenderContext::layersToRender() const
244{
245 return mLayersToRender;
246}
247
248QList<QgsMapLayer *> QgsWmsRenderContext::layers() const
249{
250 return mNicknameLayers.values();
251}
252
253QHash<const QgsMapLayer *, QStringList> QgsWmsRenderContext::acceptableLayersToRender() const
254{
255 return mAcceptableLayersToRender;
256}
257
259{
260 double denominator = -1;
261
262 if ( mScaleDenominator >= 0 )
263 {
264 denominator = mScaleDenominator;
265 }
266 else if ( mFlags & UseScaleDenominator && !mParameters.scale().isEmpty() )
267 {
268 denominator = mParameters.scaleAsDouble();
269 }
270
271 return denominator;
272}
273
275{
276 mScaleDenominator = scaleDenominator;
277 removeUnwantedLayers();
278}
279
281{
282 bool update = false;
283
284 if ( mFlags & UpdateExtent && !mParameters.bbox().isEmpty() )
285 {
286 update = true;
287 }
288
289 return update;
290}
291
293{
294 QString name = layer.serverProperties()->shortName();
295 // For external layers we cannot use the layer id because it's not known to the client, use layer name instead.
297 && std::find_if( mExternalLayers.cbegin(), mExternalLayers.cend(), [&layer]( const QgsMapLayer *l ) { return l->id() == layer.id(); } ) == mExternalLayers.cend() )
298 {
299 name = layer.id();
300 }
301 else if ( name.isEmpty() )
302 {
303 name = layer.name();
304 }
305
306 return name;
307}
308
309QgsMapLayer *QgsWmsRenderContext::layer( const QString &nickname ) const
310{
311 QgsMapLayer *mlayer = nullptr;
312
313 for ( auto layer : mLayersToRender )
314 {
315 if ( layerNickname( *layer ).compare( nickname ) == 0 )
316 {
317 mlayer = layer;
318 break;
319 }
320 }
321
322 return mlayer;
323}
324
325bool QgsWmsRenderContext::isValidLayer( const QString &nickname ) const
326{
327 return layer( nickname );
328}
329
330QList<QgsMapLayer *> QgsWmsRenderContext::layersFromGroup( const QString &nickname ) const
331{
332 return mLayerGroups.value( nickname );
333}
334
335bool QgsWmsRenderContext::isValidGroup( const QString &name ) const
336{
337 return mLayerGroups.contains( name );
338}
339
340void QgsWmsRenderContext::initNicknameLayers()
341{
342 for ( QgsMapLayer *ml : mProject->mapLayers() )
343 {
344 mNicknameLayers.insert( layerNickname( *ml ), ml );
345 }
346
347 // init groups
348 const QString rootName { QgsServerProjectUtils::wmsRootName( *mProject ) };
349 const QgsLayerTreeGroup *root = mProject->layerTreeRoot();
350
351 initLayerGroupsRecursive( root, rootName.isEmpty() ? mProject->title() : rootName );
352}
353
354void QgsWmsRenderContext::initLayerGroupsRecursive( const QgsLayerTreeGroup *group, const QString &groupName )
355{
356 if ( !groupName.isEmpty() )
357 {
358 QList<QgsMapLayer *> layerGroup;
359 const auto projectLayerTreeRoot { mProject->layerTreeRoot() };
360 const auto treeGroupLayers { group->findLayers() };
361 // Fast track if there is no custom layer order,
362 // otherwise reorder layers.
363 if ( !projectLayerTreeRoot->hasCustomLayerOrder() )
364 {
365 for ( const auto &tl : treeGroupLayers )
366 {
367 layerGroup.push_back( tl->layer() );
368 }
369 }
370 else
371 {
372 const auto projectLayerOrder { projectLayerTreeRoot->layerOrder() };
373 // Flat list containing the layers from the tree nodes
374 QList<QgsMapLayer *> groupLayersList;
375 for ( const auto &tl : treeGroupLayers )
376 {
377 groupLayersList << tl->layer();
378 }
379 for ( const auto &l : projectLayerOrder )
380 {
381 if ( groupLayersList.contains( l ) )
382 {
383 layerGroup.push_back( l );
384 }
385 }
386 }
387
388 if ( !layerGroup.empty() )
389 {
390 mLayerGroups[groupName] = layerGroup;
391 }
392 }
393
394 for ( const QgsLayerTreeNode *child : group->children() )
395 {
396 if ( child->nodeType() == QgsLayerTreeNode::NodeGroup )
397 {
398 auto group = static_cast<const QgsLayerTreeGroup *>( child );
399 QString name = group ? group->serverProperties()->shortName() : QString();
400
401 if ( name.isEmpty() )
402 name = child->name();
403
404 initLayerGroupsRecursive( group, name );
405 }
406 }
407
408 // temporal range could be already defined for a layer contained in a hierarchy of group
409 // with different default values. In that case, we assume here that this is the group higher in the
410 // hierarchy which has the precedence and set the default value.
411 // This is implied here by calling initPerLayerTemporalRange() AFTER initLayerGroupsRecursive()
412 initPerLayerTemporalRange( group, groupName );
413}
414
415void QgsWmsRenderContext::initPerLayerTemporalRange( const QgsLayerTreeGroup *group, const QString &groupName )
416{
417 if ( groupName.isEmpty() )
418 return;
419
420 // TIME parameter override time dimension default value
421 const QString timeString { mParameters.dimensionValues().value( u"TIME"_s, QString() ) };
422 if ( !timeString.isEmpty() )
423 return;
424
425 const QgsMapLayerServerProperties *serverProperties = group->serverProperties();
426 const QList<QgsMapLayerServerProperties::WmsDimensionInfo> wmsDimensions = serverProperties->wmsDimensions();
427 auto it = std::find_if( wmsDimensions.constBegin(), wmsDimensions.constEnd(), []( const QgsMapLayerServerProperties::WmsDimensionInfo &dim ) {
428 return dim.name == QgsServerWmsDimensionProperties::TIME_DIMENSION_NAME;
429 } );
430
431 if ( it != wmsDimensions.constEnd() )
432 {
433 bool wmsLayerInfosInitialized = false;
434 QMap<QString, QgsWmsLayerInfos> wmsLayerInfos;
435 QList<QgsDateTimeRange> childrenDateRanges;
436 QDateTime defaultDateTime;
437 switch ( it->defaultDisplayType )
438 {
441
442 if ( !wmsLayerInfosInitialized )
443 {
444 wmsLayerInfos = QgsWmsLayerInfos::buildWmsLayerInfos( mInterface, mProject );
445 wmsLayerInfosInitialized = true;
446 }
447
448 QgsWms::getChildRanges( group, wmsLayerInfos, mRestrictedLayers, childrenDateRanges );
449
450 defaultDateTime = it->defaultDisplayType == Qgis::WmsDimensionDefaultDisplay::MinValue ? QgsDateTimeRange::min( childrenDateRanges ) : QgsDateTimeRange::max( childrenDateRanges );
451 break;
452
454 defaultDateTime = it->referenceValue().toDateTime();
455 break;
456
458 // no default time
459 break;
460 }
461
462 if ( defaultDateTime.isValid() )
463 {
464 for ( QgsMapLayer *layer : mLayerGroups[groupName] )
465 mPerLayerTemporalRange[layer] = QgsDateTimeRange( defaultDateTime, defaultDateTime );
466 }
467 }
468}
469
470void QgsWmsRenderContext::initRestrictedLayers()
471{
472 mRestrictedLayers.clear();
473
474 // get name of restricted layers/groups in project
475 const QStringList restricted = QgsServerProjectUtils::wmsRestrictedLayers( *mProject );
476
477 // extract restricted layers from excluded groups
478 QStringList restrictedLayersNames;
479 QgsLayerTreeGroup *root = mProject->layerTreeRoot();
480
481 for ( const QString &l : std::as_const( restricted ) )
482 {
483 const QgsLayerTreeGroup *group = root->findGroup( l );
484 if ( group )
485 {
486 const QList<QgsLayerTreeLayer *> groupLayers = group->findLayers();
487 for ( QgsLayerTreeLayer *treeLayer : groupLayers )
488 {
489 restrictedLayersNames.append( treeLayer->name() );
490 }
491 }
492 else
493 {
494 restrictedLayersNames.append( l );
495 }
496 }
497
498 // build output with names, ids or short name according to the configuration
499 const QList<QgsLayerTreeLayer *> layers = root->findLayers();
500 for ( QgsLayerTreeLayer *layer : layers )
501 {
502 if ( restrictedLayersNames.contains( layer->name() ) )
503 {
504 mRestrictedLayers.append( layerNickname( *layer->layer() ) );
505 }
506 }
507}
508
509void QgsWmsRenderContext::searchLayersToRender()
510{
511 mLayersToRender.clear();
512 mStyles.clear();
513 mSlds.clear();
514
515 if ( !mParameters.sldBody().isEmpty() )
516 {
517 searchLayersToRenderSld();
518 }
519 else
520 {
521 searchLayersToRenderStyle();
522 }
523
524 QStringList nicknames;
525 if ( mFlags & AddQueryLayers )
526 nicknames << mParameters.queryLayersNickname();
527
528 if ( mFlags & AddAllLayers )
529 nicknames << mParameters.allLayersNickname();
530
531 if ( !nicknames.isEmpty() )
532 {
533 // Throw a LayerNotDefined when one of the requested layers or groups is not leading to a result, otherwise return the layers to render
534 mAcceptableLayersToRender = acceptableLayers( nicknames );
535 const QStringList queryLayerNames = flattenedQueryLayers( nicknames );
536 for ( const QString &layerName : queryLayerNames )
537 {
538 const QList<QgsMapLayer *> layers = mNicknameLayers.values( layerName );
539
540 for ( QgsMapLayer *lyr : layers )
541 {
542 if ( !mLayersToRender.contains( lyr ) )
543 {
544 if ( !mAcceptableLayersToRender.contains( lyr ) )
545 {
546 continue;
547 }
548 if ( !addLayerToRender( lyr ) )
549 {
550 throw QgsSecurityException( u"You are not allowed to access the layer %1"_s.arg( lyr->name() ) );
551 }
552 }
553 }
554 }
555 }
556}
557
558void QgsWmsRenderContext::searchLayersToRenderSld()
559{
560 const QString sld = mParameters.sldBody();
561
562 if ( sld.isEmpty() )
563 {
564 return;
565 }
566
567 QDomDocument doc;
568 ( void ) doc.setContent( sld, true );
569 QDomElement docEl = doc.documentElement();
570
571 QDomElement root = doc.firstChildElement( "StyledLayerDescriptor" );
572 QDomElement namedElem = root.firstChildElement( "NamedLayer" );
573
574 if ( docEl.isNull() )
575 {
576 return;
577 }
578
579 QDomNodeList named = docEl.elementsByTagName( "NamedLayer" );
580
581 QStringList requestedSldLayerNames;
582 for ( int i = 0; i < named.size(); ++i )
583 {
584 requestedSldLayerNames.append( named.item( i ).firstChildElement( u"Name"_s ).text() );
585 }
586
587 // Throw a LayerNotDefined when one of the requested layers or groups is not leading to a result, otherwise return the layers to render
588 mAcceptableLayersToRender = acceptableLayers( requestedSldLayerNames );
589
590 for ( int i = 0; i < named.size(); ++i )
591 {
592 QDomNodeList names = named.item( i ).toElement().elementsByTagName( "Name" );
593 if ( !names.isEmpty() )
594 {
595 QString lname = names.item( 0 ).toElement().text();
596 if ( mNicknameLayers.contains( lname ) )
597 {
598 mSlds[lname] = namedElem;
599 for ( const auto layer : mNicknameLayers.values( lname ) )
600 {
601 if ( !mAcceptableLayersToRender.contains( layer ) )
602 {
603 continue;
604 }
605 if ( !addLayerToRender( layer ) )
606 {
607 throw QgsSecurityException( u"You are not allowed to access the layer %1"_s.arg( layer->name() ) );
608 }
609 }
610 }
611 else if ( mLayerGroups.contains( lname ) )
612 {
614 {
615 QgsWmsParameter param( QgsWmsParameter::LAYER );
616 param.mValue = lname;
617 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
618 }
619
620 bool layerAdded = false;
621 for ( QgsMapLayer *layer : mLayerGroups[lname] )
622 {
623 if ( !mAcceptableLayersToRender.contains( layer ) )
624 {
625 continue;
626 }
627 // Insert only allowed layers
628 if ( checkLayerReadPermissions( layer ) )
629 {
630 const QString name = layerNickname( *layer );
631 mSlds[name] = namedElem;
632 mLayersToRender.insert( 0, layer );
633 layerAdded = true;
634 }
635 }
636 // No layers have been added, consider the group
637 // as non-existent.
638 if ( !layerAdded )
639 {
640 QgsWmsParameter param( QgsWmsParameter::LAYER );
641 param.mValue = lname;
642 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
643 }
644 }
645 else
646 {
647 QgsWmsParameter param( QgsWmsParameter::LAYER );
648 param.mValue = lname;
649 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
650 }
651 }
652 }
653}
654
655void QgsWmsRenderContext::searchLayersToRenderStyle()
656{
657 // Throw a LayerNotDefined when one of the requested layers or groups is not leading to a result, otherwise return the layers to render
658 mAcceptableLayersToRender = acceptableLayers( mParameters.allLayersNickname() );
659
660 for ( const QgsWmsParametersLayer &param : mParameters.layersParameters() )
661 {
662 const QString nickname = param.mNickname;
663 const QString style = param.mStyle;
664
665 if ( !param.mExternalUri.isEmpty() && ( mFlags & AddExternalLayers ) )
666 {
667 std::unique_ptr<QgsMapLayer> layer = std::make_unique<QgsRasterLayer>( param.mExternalUri, param.mNickname, u"wms"_s );
668
669 if ( layer->isValid() )
670 {
671 // to delete later
672 mExternalLayers.append( layer.release() );
673 auto lyr = mExternalLayers.last();
674 if ( !addLayerToRender( lyr ) )
675 {
676 throw QgsSecurityException( u"You are not allowed to access the layer %1"_s.arg( lyr->name() ) );
677 }
678 }
679 }
680 else if ( mNicknameLayers.contains( nickname ) )
681 {
682 if ( !style.isEmpty() )
683 {
684 mStyles[nickname] = style;
685 }
686
687 for ( const auto layer : mNicknameLayers.values( nickname ) )
688 {
689 if ( !mAcceptableLayersToRender.contains( layer ) )
690 {
691 continue;
692 }
693 if ( !addLayerToRender( layer ) )
694 {
695 throw QgsSecurityException( u"You are not allowed to access the layer %1"_s.arg( layer->name() ) );
696 }
697 }
698 }
699 else if ( mLayerGroups.contains( nickname ) )
700 {
702 {
703 QgsWmsParameter param( QgsWmsParameter::LAYER );
704 param.mValue = nickname;
705 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
706 }
707 // Reverse order of layers from a group
708 QList<QString> layersFromGroup;
709 for ( QgsMapLayer *layer : mLayerGroups[nickname] )
710 {
711 const QString nickname = layerNickname( *layer );
712 if ( !style.isEmpty() )
713 {
714 mStyles[nickname] = style;
715 }
716 layersFromGroup.push_front( nickname );
717 }
718
719 bool layerAdded = false;
720 for ( const auto &name : layersFromGroup )
721 {
722 for ( const auto layer : mNicknameLayers.values( name ) )
723 {
724 if ( !mAcceptableLayersToRender.contains( layer ) )
725 {
726 continue;
727 }
728 if ( addLayerToRender( layer ) )
729 {
730 layerAdded = true;
731 }
732 }
733 }
734 // No layers have been added, consider the group
735 // as non-existent.
736 if ( !layerAdded )
737 {
738 QgsWmsParameter param( QgsWmsParameter::LAYER );
739 param.mValue = nickname;
740 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
741 }
742 }
743 else
744 {
745 QgsWmsParameter param( QgsWmsParameter::LAYER );
746 param.mValue = nickname;
747 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
748 }
749 }
750}
751
752bool QgsWmsRenderContext::layerScaleVisibility( const QString &name ) const
753{
754 bool visible = false;
755
756 if ( !mNicknameLayers.contains( name ) )
757 {
758 return visible;
759 }
760
761 const QList<QgsMapLayer *> layers = mNicknameLayers.values( name );
762 for ( QgsMapLayer *layer : layers )
763 {
764 bool scaleBasedVisibility = layer->hasScaleBasedVisibility();
765 bool useScaleConstraint = ( scaleDenominator() > 0 && scaleBasedVisibility );
766
767 if ( !useScaleConstraint || layer->isInScaleRange( scaleDenominator() ) )
768 {
769 visible = true;
770 }
771 }
772
773 return visible;
774}
775
776QMap<QString, QList<QgsMapLayer *>> QgsWmsRenderContext::layerGroups() const
777{
778 return mLayerGroups;
779}
780
782{
783 int width = mParameters.widthAsInt();
784
785 // May use SRCWIDTH to define image map size
786 if ( ( mFlags & UseSrcWidthHeight ) && mParameters.srcWidthAsInt() > 0 )
787 {
788 width = mParameters.srcWidthAsInt();
789 }
790
791 return width;
792}
793
795{
796 int height = mParameters.heightAsInt();
797
798 // May use SRCHEIGHT to define image map size
799 if ( ( mFlags & UseSrcWidthHeight ) && mParameters.srcHeightAsInt() > 0 )
800 {
801 height = mParameters.srcHeightAsInt();
802 }
803
804 return height;
805}
806
811
812bool QgsWmsRenderContext::isValidWidthHeight( int width, int height ) const
813{
814 if ( width <= 0 || height <= 0 )
815 return false;
816
817 //test if maxWidth / maxHeight are set in the project or as an env variable
818 //and WIDTH / HEIGHT parameter is in the range allowed range
819 //WIDTH
820 const int wmsMaxWidthProj = QgsServerProjectUtils::wmsMaxWidth( *mProject );
821 const int wmsMaxWidthEnv = settings().wmsMaxWidth();
822 int wmsMaxWidth;
823 if ( wmsMaxWidthEnv != -1 && wmsMaxWidthProj != -1 )
824 {
825 // both are set, so we take the more conservative one
826 wmsMaxWidth = std::min( wmsMaxWidthProj, wmsMaxWidthEnv );
827 }
828 else
829 {
830 // none or one are set, so we take the bigger one which is the one set or -1
831 wmsMaxWidth = std::max( wmsMaxWidthProj, wmsMaxWidthEnv );
832 }
833
834 if ( wmsMaxWidth != -1 && width > wmsMaxWidth )
835 {
836 return false;
837 }
838
839 //HEIGHT
840 const int wmsMaxHeightProj = QgsServerProjectUtils::wmsMaxHeight( *mProject );
841 const int wmsMaxHeightEnv = settings().wmsMaxHeight();
842 int wmsMaxHeight;
843 if ( wmsMaxHeightEnv != -1 && wmsMaxHeightProj != -1 )
844 {
845 // both are set, so we take the more conservative one
846 wmsMaxHeight = std::min( wmsMaxHeightProj, wmsMaxHeightEnv );
847 }
848 else
849 {
850 // none or one are set, so we take the bigger one which is the one set or -1
851 wmsMaxHeight = std::max( wmsMaxHeightProj, wmsMaxHeightEnv );
852 }
853
854 if ( wmsMaxHeight != -1 && height > wmsMaxHeight )
855 {
856 return false;
857 }
858
859 // Sanity check from internal QImage checks
860 // (see QImageData::calculateImageParameters() in qimage_p.h)
861 // this is to report a meaningful error message in case of
862 // image creation failure and to differentiate it from out
863 // of memory conditions.
864
865 // depth for now it cannot be anything other than 32, but I don't like
866 // to hardcode it: I hope we will support other depths in the future.
867 int depth = 32;
868 switch ( mParameters.format() )
869 {
872 default:
873 depth = 32;
874 }
875
876 if ( width > ( std::numeric_limits<int>::max() - 31 ) / depth )
877 return false;
878
879 const int bytes_per_line = ( ( width * depth + 31 ) >> 5 ) << 2; // bytes per scanline (must be multiple of 4)
880
881 if ( std::numeric_limits<int>::max() / bytes_per_line < height || std::numeric_limits<int>::max() / sizeof( uchar * ) < static_cast<uint>( height ) )
882 {
883 return false;
884 }
885
886 return true;
887}
888
890{
891 double buffer;
892 if ( mFlags & UseTileBuffer )
893 {
894 const QgsRectangle extent = mParameters.bboxAsRectangle();
895 if ( !mParameters.bbox().isEmpty() && extent.isEmpty() )
896 {
898 }
899 buffer = tileBuffer() * ( extent.width() / mapWidth );
900 }
901 else
902 {
903 buffer = 0;
904 }
905 return buffer;
906}
907
908QSize QgsWmsRenderContext::mapSize( const bool aspectRatio ) const
909{
910 int width = mapWidth();
911 int height = mapHeight();
912
913 // Adapt width / height if the aspect ratio does not correspond with the BBOX.
914 // Required by WMS spec. 1.3.
915 if ( aspectRatio && mParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
916 {
917 QgsRectangle extent = mParameters.bboxAsRectangle();
918 if ( !mParameters.bbox().isEmpty() && extent.isEmpty() )
919 {
921 }
922
923 QString crs = mParameters.crs();
924 if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
925 {
926 crs = QString( "EPSG:4326" );
927 extent.invert();
928 }
929
931 if ( outputCrs.hasAxisInverted() )
932 {
933 extent.invert();
934 }
935
936 if ( !extent.isEmpty() && height > 0 && width > 0 )
937 {
938 const double mapRatio = extent.width() / extent.height();
939 const double imageRatio = static_cast<double>( width ) / static_cast<double>( height );
940 if ( !qgsDoubleNear( mapRatio, imageRatio, 0.0001 ) )
941 {
942 // inspired by MapServer, mapdraw.c L115
943 const double cellsize = ( extent.width() / static_cast<double>( width ) ) * 0.5 + ( extent.height() / static_cast<double>( height ) ) * 0.5;
944 width = extent.width() / cellsize;
945 height = extent.height() / cellsize;
946 }
947 }
948 }
949
950 if ( width <= 0 )
951 {
953 }
954 else if ( height <= 0 )
955 {
957 }
958
959 return QSize( width, height );
960}
961
962void QgsWmsRenderContext::removeUnwantedLayers()
963{
964 QList<QgsMapLayer *> layers;
965
966 for ( QgsMapLayer *layer : mLayersToRender )
967 {
968 const QString nickname = layerNickname( *layer );
969
970 if ( !isExternalLayer( nickname ) )
971 {
972 if ( !layerScaleVisibility( nickname ) )
973 continue;
974
975 if ( mRestrictedLayers.contains( nickname ) )
976 continue;
977
978 if ( mFlags & UseWfsLayersOnly )
979 {
981 {
982 continue;
983 }
984
985 const QStringList wfsLayers = QgsServerProjectUtils::wfsLayerIds( *mProject );
986 if ( !wfsLayers.contains( layer->id() ) )
987 {
988 continue;
989 }
990 }
991 }
992
993 layers.append( layer );
994 }
995
996 mLayersToRender = layers;
997}
998
999QHash<const QgsMapLayer *, QStringList> QgsWmsRenderContext::acceptableLayers( const QStringList &requestedLayerNames ) const
1000{
1001 QHash<const QgsMapLayer *, QStringList> acceptableLayersAndRequestNames;
1002 collectAcceptableLayersAndRequestNames( acceptableLayersAndRequestNames, *mProject, requestedLayerNames );
1003 bool projectIsRequested = ( requestedLayerNames.contains( QgsServerProjectUtils::wmsRootName( *mProject ) ) || requestedLayerNames.contains( mProject->title() ) );
1004
1005 if ( !projectIsRequested )
1006 {
1007 // Throw a LayerNotDefined when one of the requested layers or groups is not leading to a result
1008 auto firstFoundInacceptableLayer = std::find_if( requestedLayerNames.cbegin(), requestedLayerNames.cend(), [&]( const QString &layerName ) {
1009 //return when the requested layer has not been found as a acceptable layer
1010 return !std::any_of( acceptableLayersAndRequestNames.cbegin(), acceptableLayersAndRequestNames.cend(), [&]( const QStringList &requestedNames ) {
1011 return requestedNames.contains( layerName ) || mParameters.isExternalLayer( layerName );
1012 } );
1013 } );
1014 if ( firstFoundInacceptableLayer != requestedLayerNames.cend() )
1015 {
1016 QgsWmsParameter param( QgsWmsParameter::LAYER );
1017 param.mValue = *firstFoundInacceptableLayer;
1018 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
1019 }
1020 }
1021 return acceptableLayersAndRequestNames;
1022}
1023
1024bool QgsWmsRenderContext::isExternalLayer( const QString &name ) const
1025{
1026 for ( const auto &layer : mExternalLayers )
1027 {
1028 if ( layerNickname( *layer ).compare( name ) == 0 )
1029 return true;
1030 }
1031
1032 return false;
1033}
1034
1035bool QgsWmsRenderContext::checkLayerReadPermissions( QgsMapLayer *layer ) const
1036{
1037#ifdef HAVE_SERVER_PYTHON_PLUGINS
1038 if ( !accessControl()->layerReadPermission( layer ) )
1039 {
1040 QString msg = u"Checking forbidden access for layer: %1"_s.arg( layer->name() );
1042 return false;
1043 }
1044#endif
1045 Q_UNUSED( layer )
1046 return true;
1047}
1048
1049#ifdef HAVE_SERVER_PYTHON_PLUGINS
1050QgsAccessControl *QgsWmsRenderContext::accessControl() const
1051{
1052 return mInterface->accessControls();
1053}
1054#endif
1055
1057{
1058 mSocketFeedback = feedback;
1059}
1060
1062{
1063 return mSocketFeedback;
1064}
@ Info
Information message.
Definition qgis.h:161
@ Vector
Vector layer.
Definition qgis.h:207
@ MinValue
Display minimum value of the dimension.
Definition qgis.h:7154
@ AllValues
Display all values of the dimension.
Definition qgis.h:7153
@ MaxValue
Display maximum value of the dimension.
Definition qgis.h:7155
@ ReferenceValue
Display a reference value.
Definition qgis.h:7156
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 hasAxisInverted() const
Returns whether the axis order is inverted for the CRS compared to the order east/north (longitude/la...
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the layer tree group.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
@ NodeGroup
Container of other groups and layers.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QString id
Definition qgsmaplayer.h:86
Qgis::LayerType type
Definition qgsmaplayer.h:93
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Describes the version of a project.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:114
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
A rectangle specified with double values.
void invert()
Swap x/y coordinates in the rectangle.
Defines interfaces exposed by QGIS Server and made available to plugins.
static int wmsTileBuffer(const QgsProject &project)
Returns the tile buffer in pixels for WMS images defined in a QGIS project.
static QString wmsRootName(const QgsProject &project)
Returns the WMS root layer name defined in a QGIS project.
static bool wmsSkipNameForGroup(const QgsProject &project)
Returns if name attribute should be skipped for groups in WMS capabilities document.
static int wmsFeatureInfoPrecision(const QgsProject &project)
Returns the geometry precision for GetFeatureInfo request.
static bool wmsUseLayerIds(const QgsProject &project)
Returns if layer ids are used as name in WMS.
static QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
static bool wmsRenderMapTiles(const QgsProject &project)
Returns true if WMS requests should use the QgsMapSettings::RenderMapTile flag, so that no visible ar...
static QStringList wmsRestrictedLayers(const QgsProject &project)
Returns the restricted layer name list.
static int wmsImageQuality(const QgsProject &project)
Returns the quality for WMS images defined in a QGIS project.
static int wmsMaxWidth(const QgsProject &project)
Returns the maximum width for WMS images defined in a QGIS project.
static int wmsMaxHeight(const QgsProject &project)
Returns the maximum height for WMS images defined in a QGIS project.
Provides a way to retrieve settings by prioritizing according to environment variables,...
int wmsMaxWidth() const
Returns the server-wide max width of a WMS GetMap request.
int wmsMaxHeight() const
Returns the server-wide max height of a WMS GetMap request.
const QList< QgsServerWmsDimensionProperties::WmsDimensionInfo > wmsDimensions() const
Returns the QGIS Server WMS Dimension list.
static QDateTime min(const QList< QgsTemporalRange< QDateTime > > &ranges)
Definition qgsrange.h:674
static QMap< QString, QgsWmsLayerInfos > buildWmsLayerInfos(QgsServerInterface *serverIface, const QgsProject *project, const QList< QgsCoordinateReferenceSystem > &outputCrsList)
Returns the WMS layers definition to build WMS capabilities.
Exception thrown in case of malformed request.
Provides an interface to retrieve and manipulate WMS parameters received from the client.
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...
QgsFeedback * socketFeedback() const
Returns the response feedback if any.
QgsWmsRenderContext(const QgsProject *project, QgsServerInterface *interface)
Constructor for QgsWmsRenderContext.
QList< QgsMapLayer * > layers() const
Returns a list of all layers read from the project.
void setParameters(const QgsWmsParameters &parameters)
Sets WMS parameters.
QList< QgsMapLayer * > layersToRender() const
Returns a list of all layers to actually render according to the current configuration.
int mapWidth() const
Returns WIDTH or SRCWIDTH according to UseSrcWidthHeight flag.
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...
int tileBuffer() const
Returns the tile buffer value to use for rendering according to the current configuration.
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.
void setFlag(Flag flag, bool on=true)
Sets or unsets a rendering flag according to the on value.
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.
void setSocketFeedback(QgsFeedback *feedback)
Sets the response feedback.
double scaleDenominator() const
Returns the scale denominator to use for rendering 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.
QHash< const QgsMapLayer *, QStringList > acceptableLayersToRender() const
Returns a hash of all the layers that can be rendered and for each a list of the layer names,...
int imageQuality() const
Returns the image quality to use for rendering according to the current configuration.
int mapHeight() const
Returns HEIGHT or SRCHEIGHT according to UseSrcWidthHeight flag.
bool renderMapTiles() const
Returns true if WMS requests should use the QgsMapSettings::RenderMapTile flag, so that no visible ar...
Flag
Available rendering options.
@ AddAllLayers
For GetPrint: add layers from LAYER(S) parameter.
Median cut implementation.
void collectAcceptableLayersAndRequestNames(QHash< const QgsMapLayer *, QStringList > &acceptableLayersAndRequestNames, const QgsProject &project, const QStringList &requestedLayerNames)
Collects the acceptableLayersAndRequestNames, a hash of all the layers that can be rendered and for e...
void getChildRanges(const QgsLayerTreeGroup *layerTreeGroup, const QMap< QString, QgsWmsLayerInfos > &wmsLayerInfos, const QStringList &restrictedLayers, QList< QgsDateTimeRange > &dateRanges)
Update recursively dateRanges with all layerTreeGroup children date ranges.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7725
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition qgsrange.h:764
const double OGC_PX_M