QGIS API Documentation 4.3.0-Master (0cfde48c85b)
Loading...
Searching...
No Matches
qgswmsutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgswmsutils.cpp
3 -------------------------
4 begin : December 20 , 2016
5 copyright : (C) 2007 by Marco Hugentobler ( parts from qgswmshandler)
6 (C) 2014 by Alessandro Pasotti ( parts from qgswmshandler)
7 (C) 2016 by David Marteau
8 email : marco dot hugentobler at karto dot baug dot ethz dot ch
9 a dot pasotti at itopen dot it
10 david dot marteau at 3liz dot com
11 ***************************************************************************/
12
13/***************************************************************************
14 * *
15 * This program is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU General Public License as published by *
17 * the Free Software Foundation; either version 2 of the License, or *
18 * (at your option) any later version. *
19 * *
20 ***************************************************************************/
21
22#include "qgswmsutils.h"
23
24#include "qgslayertree.h"
26#include "qgsmediancut.h"
27#include "qgsmodule.h"
28#include "qgsproject.h"
31
32#include <QRegularExpression>
33#include <QString>
34
35using namespace Qt::StringLiterals;
36
37namespace QgsWms
38{
40 {
41 return u"1.3.0"_s;
42 }
43
44 QUrl serviceUrl( const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings )
45 {
46 QUrl href;
47 href.setUrl( QgsServerProjectUtils::wmsServiceUrl( project ? *project : *QgsProject::instance(), request, settings ) );
48
49 // Build default url
50 if ( href.isEmpty() )
51 {
52 static const QSet<QString> sFilter { u"REQUEST"_s, u"VERSION"_s, u"SERVICE"_s, u"LAYERS"_s, u"STYLES"_s, u"SLD_VERSION"_s, u"_DC"_s };
53
54 href = request.originalUrl();
55 QUrlQuery q( href );
56
57 const QList<QPair<QString, QString>> queryItems = q.queryItems();
58 for ( const QPair<QString, QString> &param : queryItems )
59 {
60 if ( sFilter.contains( param.first.toUpper() ) )
61 q.removeAllQueryItems( param.first );
62 }
63
64 href.setQuery( q );
65 }
66
67 return href;
68 }
69
70
71 ImageOutputFormat parseImageFormat( const QString &format )
72 {
73 if ( format.compare( "png"_L1, Qt::CaseInsensitive ) == 0 || format.compare( "image/png"_L1, Qt::CaseInsensitive ) == 0 )
74 {
76 }
77 else if ( format.compare( "jpg "_L1, Qt::CaseInsensitive ) == 0 || format.compare( "image/jpeg"_L1, Qt::CaseInsensitive ) == 0 )
78 {
80 }
81 else if ( format.compare( "webp"_L1, Qt::CaseInsensitive ) == 0 || format.compare( "image/webp"_L1, Qt::CaseInsensitive ) == 0 )
82 {
84 }
85 else
86 {
87 // lookup for png with mode
88 const thread_local QRegularExpression modeExpr = QRegularExpression( u"image/png\\s*;\\s*mode=([^;]+)"_s, QRegularExpression::CaseInsensitiveOption );
89
90 const QRegularExpressionMatch match = modeExpr.match( format );
91 const QString mode = match.captured( 1 );
92 if ( mode.compare( "16bit"_L1, Qt::CaseInsensitive ) == 0 )
94 if ( mode.compare( "8bit"_L1, Qt::CaseInsensitive ) == 0 )
96 if ( mode.compare( "1bit"_L1, Qt::CaseInsensitive ) == 0 )
98 }
99
101 }
102
103 // Write image response
104 void writeImage( QgsServerResponse &response, QImage &img, const QString &formatStr, int imageQuality )
105 {
106 const ImageOutputFormat outputFormat = parseImageFormat( formatStr );
107 QImage result;
108 QString saveFormat;
109 QString contentType;
110 switch ( outputFormat )
111 {
113 result = img;
114 contentType = u"image/png"_s;
115 saveFormat = u"PNG"_s;
116 break;
118 {
119 QVector<QRgb> colorTable;
120
121 // Rendering is made with the format QImage::Format_ARGB32_Premultiplied
122 // So we need to convert it in QImage::Format_ARGB32 in order to properly build
123 // the color table.
124 const QImage img256 = img.convertToFormat( QImage::Format_ARGB32 );
125 medianCut( colorTable, 256, img256 );
126 result = img256.convertToFormat( QImage::Format_Indexed8, colorTable, Qt::ColorOnly | Qt::ThresholdDither | Qt::ThresholdAlphaDither | Qt::NoOpaqueDetection );
127 }
128 contentType = u"image/png"_s;
129 saveFormat = u"PNG"_s;
130 break;
132 result = img.convertToFormat( QImage::Format_ARGB4444_Premultiplied );
133 contentType = u"image/png"_s;
134 saveFormat = u"PNG"_s;
135 break;
137 result = img.convertToFormat( QImage::Format_Mono, Qt::MonoOnly | Qt::ThresholdDither | Qt::ThresholdAlphaDither | Qt::NoOpaqueDetection );
138 contentType = u"image/png"_s;
139 saveFormat = u"PNG"_s;
140 break;
142 result = img;
143 contentType = u"image/jpeg"_s;
144 saveFormat = u"JPEG"_s;
145 break;
147 result = img;
148 contentType = u"image/webp"_s;
149 saveFormat = u"WEBP"_s;
150 break;
152 QgsMessageLog::logMessage( u"Unsupported format string %1"_s.arg( formatStr ) );
153 saveFormat = u"Unknown"_s;
154 break;
155 }
156
157 // Preserve DPI, some conversions, in particular the one for 8bit will drop this information
158 result.setDotsPerMeterX( img.dotsPerMeterX() );
159 result.setDotsPerMeterY( img.dotsPerMeterY() );
160
161 if ( outputFormat != ImageOutputFormat::Unknown )
162 {
163 response.setHeader( "Content-Type", contentType );
164 if ( saveFormat == "JPEG"_L1 || saveFormat == "WEBP"_L1 )
165 {
166 result.save( response.io(), qPrintable( saveFormat ), imageQuality );
167 }
168 else
169 {
170 result.save( response.io(), qPrintable( saveFormat ) );
171 }
172 }
173 else
174 {
176 parameter.mValue = formatStr;
178 }
179 }
180
190 QHash<const QgsMapLayer *, QStringList> &acceptableLayersAndRequestNames,
191 const QgsProject &project,
192 const QStringList &requestedLayerNames,
193 const QgsLayerTreeGroup *group,
194 QStringList requestedParentNames = QStringList(),
195 bool groupIsAnOpaqueChild = false
196 )
197 {
198 //get group nickname
199 QString groupName = group->serverProperties()->shortName();
200 if ( groupName.isEmpty() )
201 groupName = group->name();
202
203 bool projectIsRequested = ( requestedLayerNames.contains( QgsServerProjectUtils::wmsRootName( project ) ) || requestedLayerNames.contains( project.title() ) );
204 bool groupIsRequested = requestedLayerNames.contains( groupName );
205
206 // append the group to the list, when it's explicitly requested and it's not already a child of an opaque group
207 if ( groupIsRequested && !groupIsAnOpaqueChild )
208 requestedParentNames << groupName;
209
210 // the group should not be opaque or explicitly requested (by the groupname or by the project name)
211 if ( ( group->wmsGroupRequestMode() != Qgis::WmsGroupRequestMode::Opaque ) || groupIsRequested || projectIsRequested )
212 {
213 // when the current group is opaque or the previous groups have been opaque it is an opaque child and should not be requestable
214 bool isOpaqueChild = ( group->wmsGroupRequestMode() == Qgis::WmsGroupRequestMode::Opaque ) || groupIsAnOpaqueChild;
215
216 for ( QgsLayerTreeNode *child : group->children() )
217 {
218 if ( QgsLayerTree::isGroup( child ) )
219 {
220 auto subgroup = static_cast<const QgsLayerTreeGroup *>( child );
221 _collectAcceptableLayersAndRequestNames( acceptableLayersAndRequestNames, project, requestedLayerNames, subgroup, requestedParentNames, isOpaqueChild );
222 }
223 else if ( QgsLayerTree::isLayer( child ) )
224 {
225 auto layernode = static_cast<const QgsLayerTreeLayer *>( child );
226 const QgsMapLayer *layer = layernode->layer();
227 if ( !layer )
228 continue;
229
230 //get layer nickname
231 QString name = layer->serverProperties()->shortName();
233 {
234 name = layer->id();
235 }
236 else if ( name.isEmpty() )
237 {
238 name = layer->name();
239 }
240
241 QStringList requestedNames = requestedParentNames;
242 // when the layer is explicitly requested and it's not an opaque child, then add it to the requested names
243 if ( requestedLayerNames.contains( name ) && !isOpaqueChild )
244 {
245 requestedNames << name;
246 }
247 // we add the layer to the map when it's requested (or no requestedLayerNames are passed)
248 if ( !requestedNames.isEmpty() || requestedLayerNames.isEmpty() || projectIsRequested )
249 acceptableLayersAndRequestNames.insert( layer, requestedNames );
250 }
251 }
252 }
253 }
254
255 void collectAcceptableLayersAndRequestNames( QHash<const QgsMapLayer *, QStringList> &acceptableLayersAndRequestNames, const QgsProject &project, const QStringList &requestedLayerNames )
256 {
257 //Call function used for recursive collect based on the layer tree root
258 _collectAcceptableLayersAndRequestNames( acceptableLayersAndRequestNames, project, requestedLayerNames, project.layerTreeRoot() );
259 }
260
261 void getChildRanges( const QgsLayerTreeGroup *layerTreeGroup, const QMap<QString, QgsWmsLayerInfos> &wmsLayerInfos, const QStringList &restrictedLayers, QList<QgsDateTimeRange> &dateRanges )
262 {
263 QList<QgsLayerTreeNode *> layerTreeGroupChildren = layerTreeGroup->children();
264 for ( int i = 0; i < layerTreeGroupChildren.size(); ++i )
265 {
266 QgsLayerTreeNode *treeNode = layerTreeGroupChildren.at( i );
267
268 if ( treeNode->nodeType() == QgsLayerTreeNode::NodeGroup )
269 {
270 QgsLayerTreeGroup *treeGroupChild = static_cast<QgsLayerTreeGroup *>( treeNode );
271 if ( !restrictedLayers.contains( treeGroupChild->name() ) // skip restricted group
272 && treeGroupChild->hasWmsTimeDimension() )
273 {
274 QList<QgsDateTimeRange> childrenDateRanges;
275 getChildRanges( treeGroupChild, wmsLayerInfos, restrictedLayers, childrenDateRanges );
276 dateRanges.append( childrenDateRanges );
277 }
278 }
279 else
280 {
281 QgsLayerTreeLayer *treeLayer = static_cast<QgsLayerTreeLayer *>( treeNode );
282 QgsMapLayer *l = treeLayer->layer();
283
284 if ( wmsLayerInfos.contains( treeLayer->layerId() ) // layer need to be published
285 && l->temporalProperties()
286 && l->temporalProperties()->isActive() )
287 {
288 // Add all values
289 const QList<QgsDateTimeRange> allRanges { l->temporalProperties()->allTemporalRanges( l ) };
290 dateRanges.append( allRanges );
291 }
292 }
293 }
294 }
295
296
297} // namespace QgsWms
@ Opaque
Group can be requested, children cannot (appears like a single layer).
Definition qgis.h:7093
Layer tree group node serves as a container for layers and further groups.
QString name() const override
Returns the group's name.
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the layer tree group.
bool hasWmsTimeDimension() const
Returns whether the WMS time dimension should be computed for this group or not.
Qgis::WmsGroupRequestMode wmsGroupRequestMode() const
Returns the request mode of the group.
Layer tree node points to a map layer.
QString layerId() const
Returns the ID for the map layer associated with this node.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
Base class for nodes in a layer tree.
@ NodeGroup
Container of other groups and layers.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
NodeType nodeType() const
Find out about type of the node. It is usually shorter to use convenience functions from QgsLayerTree...
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
virtual QList< QgsDateTimeRange > allTemporalRanges(QgsMapLayer *layer) const
Attempts to calculate the overall list of all temporal extents which are contained in the specified l...
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
QString id
Definition qgsmaplayer.h:86
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
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).
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:114
static QgsProject * instance()
Returns the QgsProject singleton instance.
QString title
Definition qgsproject.h:117
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
static QString wmsRootName(const QgsProject &project)
Returns the WMS root layer name defined in a QGIS project.
static bool wmsUseLayerIds(const QgsProject &project)
Returns if layer ids are used as name in WMS.
static QString wmsServiceUrl(const QgsProject &project, const QgsServerRequest &request=QgsServerRequest(), const QgsServerSettings &settings=QgsServerSettings())
Returns the WMS service url.
Defines requests passed to QgsService classes.
QUrl originalUrl() const
Returns the request url as seen by the web server, by default this is equal to the url seen by QGIS s...
Defines the response interface passed to QgsService.
virtual void setHeader(const QString &key, const QString &value)=0
Set a single header value replacing any existing value(s) for the same key.
virtual QIODevice * io()=0
Returns the underlying QIODevice.
Provides a way to retrieve settings by prioritizing according to environment variables,...
bool isActive() const
Returns true if the temporal property is active.
Exception thrown in case of malformed request.
WMS parameter received from the client.
Median cut implementation.
void writeImage(QgsServerResponse &response, QImage &img, const QString &formatStr, int imageQuality)
Write image response.
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 medianCut(QVector< QRgb > &colorTable, int nColors, const QImage &inputImage)
Median cut implementation used when reducing RGB colors to palletized colors.
QString implementationVersion()
Returns the highest version supported by this implementation.
void _collectAcceptableLayersAndRequestNames(QHash< const QgsMapLayer *, QStringList > &acceptableLayersAndRequestNames, const QgsProject &project, const QStringList &requestedLayerNames, const QgsLayerTreeGroup *group, QStringList requestedParentNames=QStringList(), bool groupIsAnOpaqueChild=false)
Collects the acceptableLayersAndRequestNames recursively, a hash of all the layers that can be render...
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.
ImageOutputFormat
Supported image output format.
Definition qgswmsutils.h:43
@ Unknown
Unknown/invalid format.
Definition qgswmsutils.h:44
ImageOutputFormat parseImageFormat(const QString &format)
Parse image format parameter.
QUrl serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Returns WMS service URL.