QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgswmsgetcapabilities.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgswmsgetmap.h
3 -------------------------
4 begin : December 20 , 2016
5 copyright : (C) 2007 by Marco Hugentobler (original code)
6 (C) 2014 by Alessandro Pasotti (original code)
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#include "qgswmsutils.h"
24
25#include "qgslayoutmanager.h"
26#include "qgslayoutatlas.h"
27#include "qgsprintlayout.h"
28#include "qgslayoutitemmap.h"
29#include "qgslayoutitemlabel.h"
30#include "qgslayoutitemhtml.h"
31#include "qgslayoutframe.h"
33
36
37#include "qgsexception.h"
39#include "qgsvectorlayer.h"
41#include "qgsrasterlayer.h"
42#include "qgsrasterrenderer.h"
44
45
46namespace QgsWms
47{
48
49 namespace
50 {
51
52 void appendLayerProjectSettings( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer );
53
54 void appendDrawingOrder( QDomDocument &doc, QDomElement &parentElem, QgsServerInterface *serverIface,
55 const QgsProject *project );
56
57 void combineExtentAndCrsOfGroupChildren( QDomDocument &doc, QDomElement &groupElem, const QgsProject *project,
58 bool considerMapExtent = false );
59
60 bool crsSetFromLayerElement( const QDomElement &layerElement, QSet<QString> &crsSet );
61
62 QgsRectangle layerBoundingBoxInProjectCrs( const QDomDocument &doc, const QDomElement &layerElem,
63 const QgsProject *project );
64
65 void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent,
66 const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText,
67 const QgsProject *project );
68
69 void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent,
70 const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList,
71 const QStringList &constrainedCrsList, const QgsProject *project,
72 const QgsRectangle &geoExtent = QgsRectangle() );
73
74 void appendCrsElementToLayer( QDomDocument &doc, QDomElement &layerElement, const QDomElement &precedingElement,
75 const QString &crsText );
76
77 void appendCrsElementsToLayer( QDomDocument &doc, QDomElement &layerElement,
78 const QStringList &crsList, const QStringList &constrainedCrsList );
79
80 void appendLayerStyles( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer,
81 const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *settings );
82
83 void appendLayersFromTreeGroup( QDomDocument &doc,
84 QDomElement &parentLayer,
85 QgsServerInterface *serverIface,
86 const QgsProject *project,
87 const QgsWmsRequest &request,
88 const QgsLayerTreeGroup *layerTreeGroup,
89 bool projectSettings );
90
91 void addKeywordListElement( const QgsProject *project, QDomDocument &doc, QDomElement &parent );
92 }
93
95 const QgsProject *project,
96 const QgsWmsRequest &request,
97 QgsServerResponse &response,
98 bool projectSettings )
99 {
100#ifdef HAVE_SERVER_PYTHON_PLUGINS
101 QgsAccessControl *accessControl = serverIface->accessControls();
102#endif
103
104 QDomDocument doc;
105 const QDomDocument *capabilitiesDocument = nullptr;
106
107 // Data for WMS capabilities server memory cache
108 QString configFilePath = serverIface->configFilePath();
109 QgsCapabilitiesCache *capabilitiesCache = serverIface->capabilitiesCache();
110 QStringList cacheKeyList;
111 cacheKeyList << ( projectSettings ? QStringLiteral( "projectSettings" ) : request.wmsParameters().version() );
112 cacheKeyList << QgsServerProjectUtils::serviceUrl( request.serverParameters().service(), request, *serverIface->serverSettings() );
113 bool cache = true;
114
115#ifdef HAVE_SERVER_PYTHON_PLUGINS
116 if ( accessControl )
117 cache = accessControl->fillCacheKey( cacheKeyList );
118#endif
119 QString cacheKey = cacheKeyList.join( '-' );
120
121#ifdef HAVE_SERVER_PYTHON_PLUGINS
122 QgsServerCacheManager *cacheManager = serverIface->cacheManager();
123 if ( cacheManager && cacheManager->getCachedDocument( &doc, project, request, accessControl ) )
124 {
125 capabilitiesDocument = &doc;
126 }
127#endif
128 if ( !capabilitiesDocument && cache ) //capabilities xml not in cache plugins
129 {
130 capabilitiesDocument = capabilitiesCache->searchCapabilitiesDocument( configFilePath, cacheKey );
131 }
132
133 if ( !capabilitiesDocument ) //capabilities xml not in cache. Create a new one
134 {
135 QgsMessageLog::logMessage( QStringLiteral( "WMS capabilities document not found in cache" ), QStringLiteral( "Server" ) );
136
137 doc = getCapabilities( serverIface, project, request, projectSettings );
138
139#ifdef HAVE_SERVER_PYTHON_PLUGINS
140 if ( cacheManager &&
141 cacheManager->setCachedDocument( &doc, project, request, accessControl ) )
142 {
143 capabilitiesDocument = &doc;
144 }
145#endif
146
147 // cppcheck-suppress identicalInnerCondition
148 if ( !capabilitiesDocument )
149 {
150 capabilitiesCache->insertCapabilitiesDocument( configFilePath, cacheKey, &doc );
151 capabilitiesDocument = capabilitiesCache->searchCapabilitiesDocument( configFilePath, cacheKey );
152 }
153 if ( !capabilitiesDocument )
154 {
155 capabilitiesDocument = &doc;
156 }
157 else
158 {
159 QgsMessageLog::logMessage( QStringLiteral( "Set WMS capabilities document in cache" ), QStringLiteral( "Server" ) );
160 }
161 }
162 else
163 {
164 QgsMessageLog::logMessage( QStringLiteral( "Found WMS capabilities document in cache" ), QStringLiteral( "Server" ) );
165 }
166
167 response.setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/xml; charset=utf-8" ) );
168 response.write( capabilitiesDocument->toByteArray() );
169 }
170
171 QDomDocument getCapabilities( QgsServerInterface *serverIface, const QgsProject *project,
172 const QgsWmsRequest &request,
173 bool projectSettings )
174 {
175 QDomDocument doc;
176 QDomElement wmsCapabilitiesElement;
177
178 // Get service URL
179 QUrl href = serviceUrl( request, project, *serverIface->serverSettings() );
180
181 //href needs to be a prefix
182 QString hrefString = href.toString();
183 hrefString.append( href.hasQuery() ? "&" : "?" );
184
185 // XML declaration
186 QDomProcessingInstruction xmlDeclaration = doc.createProcessingInstruction( QStringLiteral( "xml" ),
187 QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) );
188
189 // Append format helper
190 std::function < void ( QDomElement &, const QString & ) > appendFormat = [&doc]( QDomElement & elem, const QString & format )
191 {
192 QDomElement formatElem = doc.createElement( QStringLiteral( "Format" )/*wms:Format*/ );
193 formatElem.appendChild( doc.createTextNode( format ) );
194 elem.appendChild( formatElem );
195 };
196
197 if ( request.wmsParameters().version() == QLatin1String( "1.1.1" ) )
198 {
199 doc = QDomDocument( QStringLiteral( "WMT_MS_Capabilities SYSTEM 'http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd'" ) ); //WMS 1.1.1 needs DOCTYPE "SYSTEM http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd"
200 doc.appendChild( xmlDeclaration );
201 wmsCapabilitiesElement = doc.createElement( QStringLiteral( "WMT_MS_Capabilities" )/*wms:WMS_Capabilities*/ );
202 }
203 else // 1.3.0 as default
204 {
205 doc.appendChild( xmlDeclaration );
206 wmsCapabilitiesElement = doc.createElement( QStringLiteral( "WMS_Capabilities" )/*wms:WMS_Capabilities*/ );
207 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns" ), QStringLiteral( "http://www.opengis.net/wms" ) );
208 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:sld" ), QStringLiteral( "http://www.opengis.net/sld" ) );
209 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://www.qgis.org/wms" ) );
210 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
211 QString schemaLocation = QStringLiteral( "http://www.opengis.net/wms" );
212 schemaLocation += QLatin1String( " http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd" );
213 schemaLocation += QLatin1String( " http://www.opengis.net/sld" );
214 schemaLocation += QLatin1String( " http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd" );
215
217 {
218 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:inspire_common" ), QStringLiteral( "http://inspire.ec.europa.eu/schemas/common/1.0" ) );
219 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:inspire_vs" ), QStringLiteral( "http://inspire.ec.europa.eu/schemas/inspire_vs/1.0" ) );
220 schemaLocation += QLatin1String( " http://inspire.ec.europa.eu/schemas/inspire_vs/1.0" );
221 schemaLocation += QLatin1String( " http://inspire.ec.europa.eu/schemas/inspire_vs/1.0/inspire_vs.xsd" );
222 }
223
224 schemaLocation += QLatin1String( " http://www.qgis.org/wms" );
225 schemaLocation += " " + hrefString + "SERVICE=WMS&REQUEST=GetSchemaExtension";
226
227 wmsCapabilitiesElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), schemaLocation );
228 }
229 wmsCapabilitiesElement.setAttribute( QStringLiteral( "version" ), request.wmsParameters().version() );
230 doc.appendChild( wmsCapabilitiesElement );
231
232 //INSERT Service
233 wmsCapabilitiesElement.appendChild( getServiceElement( doc, project, request, serverIface->serverSettings() ) );
234
235 //wms:Capability element
236 QDomElement capabilityElement = getCapabilityElement( doc, project, request, projectSettings, serverIface );
237 wmsCapabilitiesElement.appendChild( capabilityElement );
238
239 if ( projectSettings )
240 {
241 //Insert <ComposerTemplate> elements derived from wms:_ExtendedCapabilities
242 capabilityElement.appendChild( getComposerTemplatesElement( doc, project ) );
243
244 //WFS layers
245 capabilityElement.appendChild( getWFSLayersElement( doc, project ) );
246 }
247
248 capabilityElement.appendChild(
249 getLayersAndStylesCapabilitiesElement( doc, serverIface, project, request, projectSettings )
250 );
251
252 if ( projectSettings )
253 {
254 appendDrawingOrder( doc, capabilityElement, serverIface, project );
255 }
256
257 return doc;
258 }
259
260 QDomElement getServiceElement( QDomDocument &doc, const QgsProject *project,
261 const QgsWmsRequest &request, const QgsServerSettings *serverSettings )
262 {
263 //Service element
264 QDomElement serviceElem = doc.createElement( QStringLiteral( "Service" ) );
265
266 //Service name
267 QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
268 QDomText nameText = doc.createTextNode( QStringLiteral( "WMS" ) );
269 nameElem.appendChild( nameText );
270 serviceElem.appendChild( nameElem );
271
272 // Service title
273 QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) );
274 QDomText titleText = doc.createTextNode( QgsServerProjectUtils::owsServiceTitle( *project ) );
275 titleElem.appendChild( titleText );
276 serviceElem.appendChild( titleElem );
277
278 QString abstract = QgsServerProjectUtils::owsServiceAbstract( *project );
279 if ( !abstract.isEmpty() )
280 {
281 QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) );
282 QDomText abstractText = doc.createCDATASection( abstract );
283 abstractElem.appendChild( abstractText );
284 serviceElem.appendChild( abstractElem );
285 }
286
287 addKeywordListElement( project, doc, serviceElem );
288
289 QString onlineResource = QgsServerProjectUtils::owsServiceOnlineResource( *project );
290 if ( onlineResource.isEmpty() )
291 {
292 onlineResource = serviceUrl( request, project, *serverSettings ).toString();
293 }
294 QDomElement onlineResourceElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
295 onlineResourceElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
296 onlineResourceElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
297 onlineResourceElem.setAttribute( QStringLiteral( "xlink:href" ), onlineResource );
298 serviceElem.appendChild( onlineResourceElem );
299
300 QString contactPerson = QgsServerProjectUtils::owsServiceContactPerson( *project );
301 QString contactOrganization = QgsServerProjectUtils::owsServiceContactOrganization( *project );
302 QString contactPosition = QgsServerProjectUtils::owsServiceContactPosition( *project );
303 QString contactMail = QgsServerProjectUtils::owsServiceContactMail( *project );
304 QString contactPhone = QgsServerProjectUtils::owsServiceContactPhone( *project );
305 if ( !contactPerson.isEmpty() ||
306 !contactOrganization.isEmpty() ||
307 !contactPosition.isEmpty() ||
308 !contactMail.isEmpty() ||
309 !contactPhone.isEmpty() )
310 {
311 //Contact information
312 QDomElement contactInfoElem = doc.createElement( QStringLiteral( "ContactInformation" ) );
313
314 //Contact person primary
315 if ( !contactPerson.isEmpty() ||
316 !contactOrganization.isEmpty() )
317 {
318 QDomElement contactPersonPrimaryElem = doc.createElement( QStringLiteral( "ContactPersonPrimary" ) );
319
320 QDomText contactPersonText;
321 if ( !contactPerson.isEmpty() )
322 {
323 contactPersonText = doc.createTextNode( contactPerson );
324 }
325 else
326 {
327 contactPersonText = doc.createTextNode( QStringLiteral( "unknown" ) );
328 }
329 QDomElement contactPersonElem = doc.createElement( QStringLiteral( "ContactPerson" ) );
330 contactPersonElem.appendChild( contactPersonText );
331 contactPersonPrimaryElem.appendChild( contactPersonElem );
332
333 QDomText contactOrganizationText;
334 if ( !contactOrganization.isEmpty() )
335 {
336 contactOrganizationText = doc.createTextNode( contactOrganization );
337 }
338 else
339 {
340 contactOrganizationText = doc.createTextNode( QStringLiteral( "unknown" ) );
341 }
342 QDomElement contactOrganizationElem = doc.createElement( QStringLiteral( "ContactOrganization" ) );
343 contactOrganizationElem.appendChild( contactOrganizationText );
344 contactPersonPrimaryElem.appendChild( contactOrganizationElem );
345
346 contactInfoElem.appendChild( contactPersonPrimaryElem );
347 }
348
349 if ( !contactPosition.isEmpty() )
350 {
351 QDomElement contactPositionElem = doc.createElement( QStringLiteral( "ContactPosition" ) );
352 QDomText contactPositionText = doc.createTextNode( contactPosition );
353 contactPositionElem.appendChild( contactPositionText );
354 contactInfoElem.appendChild( contactPositionElem );
355 }
356
357 if ( !contactPhone.isEmpty() )
358 {
359 QDomElement phoneElem = doc.createElement( QStringLiteral( "ContactVoiceTelephone" ) );
360 QDomText phoneText = doc.createTextNode( contactPhone );
361 phoneElem.appendChild( phoneText );
362 contactInfoElem.appendChild( phoneElem );
363 }
364
365 if ( !contactMail.isEmpty() )
366 {
367 QDomElement mailElem = doc.createElement( QStringLiteral( "ContactElectronicMailAddress" ) );
368 QDomText mailText = doc.createTextNode( contactMail );
369 mailElem.appendChild( mailText );
370 contactInfoElem.appendChild( mailElem );
371 }
372
373 serviceElem.appendChild( contactInfoElem );
374 }
375
376 QDomElement feesElem = doc.createElement( QStringLiteral( "Fees" ) );
377 QDomText feesText = doc.createTextNode( QStringLiteral( "None" ) ); // default value if fees are unknown
378 QString fees = QgsServerProjectUtils::owsServiceFees( *project );
379 if ( !fees.isEmpty() )
380 {
381 feesText = doc.createTextNode( fees );
382 }
383 feesElem.appendChild( feesText );
384 serviceElem.appendChild( feesElem );
385
386 QDomElement accessConstraintsElem = doc.createElement( QStringLiteral( "AccessConstraints" ) );
387 QDomText accessConstraintsText = doc.createTextNode( QStringLiteral( "None" ) ); // default value if access constraints are unknown
388 QString accessConstraints = QgsServerProjectUtils::owsServiceAccessConstraints( *project );
389 if ( !accessConstraints.isEmpty() )
390 {
391 accessConstraintsText = doc.createTextNode( accessConstraints );
392 }
393 accessConstraintsElem.appendChild( accessConstraintsText );
394 serviceElem.appendChild( accessConstraintsElem );
395
396 if ( request.wmsParameters().version() == QLatin1String( "1.3.0" ) )
397 {
398 int maxWidth = QgsServerProjectUtils::wmsMaxWidth( *project );
399 if ( maxWidth > 0 )
400 {
401 QDomElement maxWidthElem = doc.createElement( QStringLiteral( "MaxWidth" ) );
402 QDomText maxWidthText = doc.createTextNode( QString::number( maxWidth ) );
403 maxWidthElem.appendChild( maxWidthText );
404 serviceElem.appendChild( maxWidthElem );
405 }
406
407 int maxHeight = QgsServerProjectUtils::wmsMaxHeight( *project );
408 if ( maxHeight > 0 )
409 {
410 QDomElement maxHeightElem = doc.createElement( QStringLiteral( "MaxHeight" ) );
411 QDomText maxHeightText = doc.createTextNode( QString::number( maxHeight ) );
412 maxHeightElem.appendChild( maxHeightText );
413 serviceElem.appendChild( maxHeightElem );
414 }
415 }
416
417 return serviceElem;
418 }
419
420 QDomElement getCapabilityElement( QDomDocument &doc, const QgsProject *project,
421 const QgsWmsRequest &request,
422 bool projectSettings, QgsServerInterface *serverIface )
423 {
424 const QString version = request.wmsParameters().version();
425
426 // Get service URL
427 QUrl href = serviceUrl( request, project, *serverIface->serverSettings() );
428
429 //href needs to be a prefix
430 QString hrefString = href.toString();
431 hrefString.append( href.hasQuery() ? "&" : "?" );
432
433 QDomElement capabilityElem = doc.createElement( QStringLiteral( "Capability" )/*wms:Capability*/ );
434
435 //wms:Request element
436 QDomElement requestElem = doc.createElement( QStringLiteral( "Request" )/*wms:Request*/ );
437 capabilityElem.appendChild( requestElem );
438
439 QDomElement dcpTypeElem = doc.createElement( QStringLiteral( "DCPType" )/*wms:DCPType*/ );
440 QDomElement httpElem = doc.createElement( QStringLiteral( "HTTP" )/*wms:HTTP*/ );
441 dcpTypeElem.appendChild( httpElem );
442
443 // Append format helper
444 std::function < void ( QDomElement &, const QString & ) > appendFormat = [&doc]( QDomElement & elem, const QString & format )
445 {
446 QDomElement formatElem = doc.createElement( QStringLiteral( "Format" )/*wms:Format*/ );
447 formatElem.appendChild( doc.createTextNode( format ) );
448 elem.appendChild( formatElem );
449 };
450
451 QDomElement elem;
452
453 //wms:GetCapabilities
454 elem = doc.createElement( QStringLiteral( "GetCapabilities" )/*wms:GetCapabilities*/ );
455 appendFormat( elem, ( version == QLatin1String( "1.1.1" ) ? "application/vnd.ogc.wms_xml" : "text/xml" ) );
456 elem.appendChild( dcpTypeElem );
457 requestElem.appendChild( elem );
458
459 //only Get supported for the moment
460 QDomElement getElem = doc.createElement( QStringLiteral( "Get" )/*wms:Get*/ );
461 httpElem.appendChild( getElem );
462 QDomElement olResourceElem = doc.createElement( QStringLiteral( "OnlineResource" )/*wms:OnlineResource*/ );
463 olResourceElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
464 olResourceElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
465 olResourceElem.setAttribute( QStringLiteral( "xlink:href" ), hrefString );
466 getElem.appendChild( olResourceElem );
467
468 //wms:GetMap
469 elem = doc.createElement( QStringLiteral( "GetMap" )/*wms:GetMap*/ );
470 appendFormat( elem, QStringLiteral( "image/jpeg" ) );
471 appendFormat( elem, QStringLiteral( "image/png" ) );
472 appendFormat( elem, QStringLiteral( "image/png; mode=16bit" ) );
473 appendFormat( elem, QStringLiteral( "image/png; mode=8bit" ) );
474 appendFormat( elem, QStringLiteral( "image/png; mode=1bit" ) );
475 appendFormat( elem, QStringLiteral( "application/dxf" ) );
476 elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
477 requestElem.appendChild( elem );
478
479 //wms:GetFeatureInfo
480 elem = doc.createElement( QStringLiteral( "GetFeatureInfo" ) );
481 appendFormat( elem, QStringLiteral( "text/plain" ) );
482 appendFormat( elem, QStringLiteral( "text/html" ) );
483 appendFormat( elem, QStringLiteral( "text/xml" ) );
484 appendFormat( elem, QStringLiteral( "application/vnd.ogc.gml" ) );
485 appendFormat( elem, QStringLiteral( "application/vnd.ogc.gml/3.1.1" ) );
486 appendFormat( elem, QStringLiteral( "application/json" ) );
487 appendFormat( elem, QStringLiteral( "application/geo+json" ) );
488 elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
489 requestElem.appendChild( elem );
490
491 //wms:GetLegendGraphic
492 elem = doc.createElement( ( version == QLatin1String( "1.1.1" ) ? "GetLegendGraphic" : "sld:GetLegendGraphic" )/*wms:GetLegendGraphic*/ );
493 appendFormat( elem, QStringLiteral( "image/jpeg" ) );
494 appendFormat( elem, QStringLiteral( "image/png" ) );
495 appendFormat( elem, QStringLiteral( "application/json" ) );
496 elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
497 requestElem.appendChild( elem );
498
499 //wms:DescribeLayer
500 elem = doc.createElement( ( version == QLatin1String( "1.1.1" ) ? "DescribeLayer" : "sld:DescribeLayer" )/*wms:GetLegendGraphic*/ );
501 appendFormat( elem, QStringLiteral( "text/xml" ) );
502 elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
503 requestElem.appendChild( elem );
504
505 //wms:GetStyles
506 elem = doc.createElement( ( version == QLatin1String( "1.1.1" ) ? "GetStyles" : "qgs:GetStyles" )/*wms:GetStyles*/ );
507 appendFormat( elem, QStringLiteral( "text/xml" ) );
508 elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
509 requestElem.appendChild( elem );
510
511 if ( ( !serverIface->serverSettings() || !serverIface->serverSettings()->getPrintDisabled() ) &&
512 projectSettings ) //remove composer templates from GetCapabilities in the long term
513 {
514 //wms:GetPrint
515 elem = doc.createElement( QStringLiteral( "GetPrint" ) /*wms:GetPrint*/ );
516 appendFormat( elem, QStringLiteral( "svg" ) );
517 appendFormat( elem, QStringLiteral( "png" ) );
518 appendFormat( elem, QStringLiteral( "pdf" ) );
519 elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
520 requestElem.appendChild( elem );
521 }
522
523 //Exception element is mandatory
524 elem = doc.createElement( QStringLiteral( "Exception" ) );
525 appendFormat( elem, ( version == QLatin1String( "1.1.1" ) ? "application/vnd.ogc.se_xml" : "XML" ) );
526 capabilityElem.appendChild( elem );
527
528 //UserDefinedSymbolization element
529 if ( version == QLatin1String( "1.3.0" ) )
530 {
531 elem = doc.createElement( QStringLiteral( "sld:UserDefinedSymbolization" ) );
532 elem.setAttribute( QStringLiteral( "SupportSLD" ), QStringLiteral( "1" ) );
533 elem.setAttribute( QStringLiteral( "UserLayer" ), QStringLiteral( "0" ) );
534 elem.setAttribute( QStringLiteral( "UserStyle" ), QStringLiteral( "1" ) );
535 elem.setAttribute( QStringLiteral( "RemoteWFS" ), QStringLiteral( "0" ) );
536 elem.setAttribute( QStringLiteral( "InlineFeature" ), QStringLiteral( "0" ) );
537 elem.setAttribute( QStringLiteral( "RemoteWCS" ), QStringLiteral( "0" ) );
538 capabilityElem.appendChild( elem );
539
541 {
542 capabilityElem.appendChild( getInspireCapabilitiesElement( doc, project ) );
543 }
544 }
545
546 return capabilityElem;
547 }
548
549 QDomElement getInspireCapabilitiesElement( QDomDocument &doc, const QgsProject *project )
550 {
551 QDomElement inspireCapabilitiesElem;
552
554 return inspireCapabilitiesElem;
555
556 inspireCapabilitiesElem = doc.createElement( QStringLiteral( "inspire_vs:ExtendedCapabilities" ) );
557
558 QString inspireMetadataUrl = QgsServerProjectUtils::wmsInspireMetadataUrl( *project );
559 // inspire scenario 1
560 if ( !inspireMetadataUrl.isEmpty() )
561 {
562 QDomElement inspireCommonMetadataUrlElem = doc.createElement( QStringLiteral( "inspire_common:MetadataUrl" ) );
563 inspireCommonMetadataUrlElem.setAttribute( QStringLiteral( "xsi:type" ), QStringLiteral( "inspire_common:resourceLocatorType" ) );
564
565 QDomElement inspireCommonMetadataUrlUrlElem = doc.createElement( QStringLiteral( "inspire_common:URL" ) );
566 inspireCommonMetadataUrlUrlElem.appendChild( doc.createTextNode( inspireMetadataUrl ) );
567 inspireCommonMetadataUrlElem.appendChild( inspireCommonMetadataUrlUrlElem );
568
569 QString inspireMetadataUrlType = QgsServerProjectUtils::wmsInspireMetadataUrlType( *project );
570 if ( !inspireMetadataUrlType.isNull() )
571 {
572 QDomElement inspireCommonMetadataUrlMediaTypeElem = doc.createElement( QStringLiteral( "inspire_common:MediaType" ) );
573 inspireCommonMetadataUrlMediaTypeElem.appendChild( doc.createTextNode( inspireMetadataUrlType ) );
574 inspireCommonMetadataUrlElem.appendChild( inspireCommonMetadataUrlMediaTypeElem );
575 }
576
577 inspireCapabilitiesElem.appendChild( inspireCommonMetadataUrlElem );
578 }
579 else
580 {
581 QDomElement inspireCommonResourceTypeElem = doc.createElement( QStringLiteral( "inspire_common:ResourceType" ) );
582 inspireCommonResourceTypeElem.appendChild( doc.createTextNode( QStringLiteral( "service" ) ) );
583 inspireCapabilitiesElem.appendChild( inspireCommonResourceTypeElem );
584
585 QDomElement inspireCommonSpatialDataServiceTypeElem = doc.createElement( QStringLiteral( "inspire_common:SpatialDataServiceType" ) );
586 inspireCommonSpatialDataServiceTypeElem.appendChild( doc.createTextNode( QStringLiteral( "view" ) ) );
587 inspireCapabilitiesElem.appendChild( inspireCommonSpatialDataServiceTypeElem );
588
589 QString inspireTemporalReference = QgsServerProjectUtils::wmsInspireTemporalReference( *project );
590 if ( !inspireTemporalReference.isNull() )
591 {
592 QDomElement inspireCommonTemporalReferenceElem = doc.createElement( QStringLiteral( "inspire_common:TemporalReference" ) );
593 QDomElement inspireCommonDateOfLastRevisionElem = doc.createElement( QStringLiteral( "inspire_common:DateOfLastRevision" ) );
594 inspireCommonDateOfLastRevisionElem.appendChild( doc.createTextNode( inspireTemporalReference ) );
595 inspireCommonTemporalReferenceElem.appendChild( inspireCommonDateOfLastRevisionElem );
596 inspireCapabilitiesElem.appendChild( inspireCommonTemporalReferenceElem );
597 }
598
599 QDomElement inspireCommonMetadataPointOfContactElem = doc.createElement( QStringLiteral( "inspire_common:MetadataPointOfContact" ) );
600
601 QString contactOrganization = QgsServerProjectUtils::owsServiceContactOrganization( *project );
602 QDomElement inspireCommonOrganisationNameElem = doc.createElement( QStringLiteral( "inspire_common:OrganisationName" ) );
603 if ( !contactOrganization.isNull() )
604 {
605 inspireCommonOrganisationNameElem.appendChild( doc.createTextNode( contactOrganization ) );
606 }
607 inspireCommonMetadataPointOfContactElem.appendChild( inspireCommonOrganisationNameElem );
608
609 QString contactMail = QgsServerProjectUtils::owsServiceContactMail( *project );
610 QDomElement inspireCommonEmailAddressElem = doc.createElement( QStringLiteral( "inspire_common:EmailAddress" ) );
611 if ( !contactMail.isNull() )
612 {
613 inspireCommonEmailAddressElem.appendChild( doc.createTextNode( contactMail ) );
614 }
615 inspireCommonMetadataPointOfContactElem.appendChild( inspireCommonEmailAddressElem );
616
617 inspireCapabilitiesElem.appendChild( inspireCommonMetadataPointOfContactElem );
618
619 QString inspireMetadataDate = QgsServerProjectUtils::wmsInspireMetadataDate( *project );
620 if ( !inspireMetadataDate.isNull() )
621 {
622 QDomElement inspireCommonMetadataDateElem = doc.createElement( QStringLiteral( "inspire_common:MetadataDate" ) );
623 inspireCommonMetadataDateElem.appendChild( doc.createTextNode( inspireMetadataDate ) );
624 inspireCapabilitiesElem.appendChild( inspireCommonMetadataDateElem );
625 }
626 }
627
628 // Supported languages
629 QDomElement inspireCommonSupportedLanguagesElem = doc.createElement( QStringLiteral( "inspire_common:SupportedLanguages" ) );
630 inspireCommonSupportedLanguagesElem.setAttribute( QStringLiteral( "xsi:type" ), QStringLiteral( "inspire_common:supportedLanguagesType" ) );
631
632 QDomElement inspireCommonLanguageElem = doc.createElement( QStringLiteral( "inspire_common:Language" ) );
633 inspireCommonLanguageElem.appendChild( doc.createTextNode( QgsServerProjectUtils::wmsInspireLanguage( *project ) ) );
634
635 QDomElement inspireCommonDefaultLanguageElem = doc.createElement( QStringLiteral( "inspire_common:DefaultLanguage" ) );
636 inspireCommonDefaultLanguageElem.appendChild( inspireCommonLanguageElem );
637 inspireCommonSupportedLanguagesElem.appendChild( inspireCommonDefaultLanguageElem );
638
639#if 0
640 /* Supported language has to be different from default one */
641 QDomElement inspireCommonSupportedLanguageElem = doc.createElement( "inspire_common:SupportedLanguage" );
642 inspireCommonSupportedLanguageElem.appendChild( inspireCommonLanguageElem.cloneNode().toElement() );
643 inspireCommonSupportedLanguagesElem.appendChild( inspireCommonSupportedLanguageElem );
644#endif
645
646 inspireCapabilitiesElem.appendChild( inspireCommonSupportedLanguagesElem );
647
648 QDomElement inspireCommonResponseLanguageElem = doc.createElement( QStringLiteral( "inspire_common:ResponseLanguage" ) );
649 inspireCommonResponseLanguageElem.appendChild( inspireCommonLanguageElem.cloneNode().toElement() );
650 inspireCapabilitiesElem.appendChild( inspireCommonResponseLanguageElem );
651
652 return inspireCapabilitiesElem;
653 }
654
655 QDomElement getComposerTemplatesElement( QDomDocument &doc, const QgsProject *project )
656 {
657 QList< QgsPrintLayout * > projectComposers = project->layoutManager()->printLayouts();
658 if ( projectComposers.size() == 0 )
659 return QDomElement();
660
661 QStringList restrictedComposers = QgsServerProjectUtils::wmsRestrictedComposers( *project );
662
663 QDomElement composerTemplatesElem = doc.createElement( QStringLiteral( "ComposerTemplates" ) );
664 QList<QgsPrintLayout *>::const_iterator cIt = projectComposers.constBegin();
665 for ( ; cIt != projectComposers.constEnd(); ++cIt )
666 {
667 QgsPrintLayout *layout = *cIt;
668 if ( restrictedComposers.contains( layout->name() ) )
669 continue;
670
671 // Check that we have at least one page
672 if ( layout->pageCollection()->pageCount() < 1 )
673 continue;
674
675 // Get width and height from first page of the collection
676 QgsLayoutSize layoutSize( layout->pageCollection()->page( 0 )->sizeWithUnits() );
677 QgsLayoutMeasurement width( layout->convertFromLayoutUnits( layoutSize.width(), QgsUnitTypes::LayoutUnit::LayoutMillimeters ) );
678 QgsLayoutMeasurement height( layout->convertFromLayoutUnits( layoutSize.height(), QgsUnitTypes::LayoutUnit::LayoutMillimeters ) );
679
680 QDomElement composerTemplateElem = doc.createElement( QStringLiteral( "ComposerTemplate" ) );
681 composerTemplateElem.setAttribute( QStringLiteral( "name" ), layout->name() );
682
683 //get paper width and height in mm from composition
684 composerTemplateElem.setAttribute( QStringLiteral( "width" ), width.length() );
685 composerTemplateElem.setAttribute( QStringLiteral( "height" ), height.length() );
686
687 //atlas enabled and atlas covering layer
688 QgsLayoutAtlas *atlas = layout->atlas();
689 if ( atlas && atlas->enabled() )
690 {
691 composerTemplateElem.setAttribute( QStringLiteral( "atlasEnabled" ), QStringLiteral( "1" ) );
692 QgsVectorLayer *cLayer = atlas->coverageLayer();
693 if ( cLayer )
694 {
695 QString layerName = cLayer->shortName();
697 {
698 layerName = cLayer->id();
699 }
700 else if ( layerName.isEmpty() )
701 {
702 layerName = cLayer->name();
703 }
704 composerTemplateElem.setAttribute( QStringLiteral( "atlasCoverageLayer" ), layerName );
705 }
706 }
707
708 //add available composer maps and their size in mm
709 QList<QgsLayoutItemMap *> layoutMapList;
710 layout->layoutItems<QgsLayoutItemMap>( layoutMapList );
711 QList<QgsLayoutItemMap *>::const_iterator cmIt = layoutMapList.constBegin();
712 // Add map id
713 int mapId = 0;
714 for ( ; cmIt != layoutMapList.constEnd(); ++cmIt )
715 {
716 const QgsLayoutItemMap *composerMap = *cmIt;
717
718 QDomElement composerMapElem = doc.createElement( QStringLiteral( "ComposerMap" ) );
719 composerMapElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "map%1" ).arg( mapId ) );
720 composerMapElem.setAttribute( QStringLiteral( "itemName" ), composerMap->displayName() );
721 mapId++;
722 composerMapElem.setAttribute( QStringLiteral( "width" ), composerMap->rect().width() );
723 composerMapElem.setAttribute( QStringLiteral( "height" ), composerMap->rect().height() );
724 composerTemplateElem.appendChild( composerMapElem );
725 }
726
727 //add available composer labels
728 QList<QgsLayoutItemLabel *> composerLabelList;
729 layout->layoutItems<QgsLayoutItemLabel>( composerLabelList );
730 QList<QgsLayoutItemLabel *>::const_iterator clIt = composerLabelList.constBegin();
731 for ( ; clIt != composerLabelList.constEnd(); ++clIt )
732 {
733 QgsLayoutItemLabel *composerLabel = *clIt;
734 QString id = composerLabel->id();
735 if ( id.isEmpty() )
736 continue;
737
738 QDomElement composerLabelElem = doc.createElement( QStringLiteral( "ComposerLabel" ) );
739 composerLabelElem.setAttribute( QStringLiteral( "name" ), id );
740 composerTemplateElem.appendChild( composerLabelElem );
741 }
742
743 //add available composer HTML
744 QList<QgsLayoutItemHtml *> composerHtmlList;
745 layout->layoutObjects<QgsLayoutItemHtml>( composerHtmlList );
746 QList<QgsLayoutItemHtml *>::const_iterator chIt = composerHtmlList.constBegin();
747 for ( ; chIt != composerHtmlList.constEnd(); ++chIt )
748 {
749 QgsLayoutItemHtml *composerHtml = *chIt;
750 if ( composerHtml->frameCount() == 0 )
751 continue;
752
753 QString id = composerHtml->frame( 0 )->id();
754 if ( id.isEmpty() )
755 continue;
756
757 QDomElement composerHtmlElem = doc.createElement( QStringLiteral( "ComposerHtml" ) );
758 composerHtmlElem.setAttribute( QStringLiteral( "name" ), id );
759 composerTemplateElem.appendChild( composerHtmlElem );
760 }
761
762 composerTemplatesElem.appendChild( composerTemplateElem );
763 }
764
765 if ( composerTemplatesElem.childNodes().size() == 0 )
766 return QDomElement();
767
768 return composerTemplatesElem;
769 }
770
771 QDomElement getWFSLayersElement( QDomDocument &doc, const QgsProject *project )
772 {
773 QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
774 if ( wfsLayerIds.size() == 0 )
775 return QDomElement();
776
777 QDomElement wfsLayersElem = doc.createElement( QStringLiteral( "WFSLayers" ) );
778 for ( int i = 0; i < wfsLayerIds.size(); ++i )
779 {
780 QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) );
781 if ( ! layer || layer->type() != QgsMapLayerType::VectorLayer )
782 {
783 continue;
784 }
785
786 QDomElement wfsLayerElem = doc.createElement( QStringLiteral( "WFSLayer" ) );
788 {
789 wfsLayerElem.setAttribute( QStringLiteral( "name" ), layer->id() );
790 }
791 else
792 {
793 wfsLayerElem.setAttribute( QStringLiteral( "name" ), layer->name() );
794 }
795 wfsLayersElem.appendChild( wfsLayerElem );
796 }
797
798 return wfsLayersElem;
799 }
800
801 QDomElement getLayersAndStylesCapabilitiesElement( QDomDocument &doc, QgsServerInterface *serverIface,
802 const QgsProject *project,
803 const QgsWmsRequest &request, bool projectSettings )
804 {
805 const QgsLayerTree *projectLayerTreeRoot = project->layerTreeRoot();
806
807 QDomElement layerParentElem = doc.createElement( QStringLiteral( "Layer" ) );
808
809 // Root Layer name
810 QString rootLayerName = QgsServerProjectUtils::wmsRootName( *project );
811 if ( rootLayerName.isEmpty() && !project->title().isEmpty() )
812 {
813 rootLayerName = project->title();
814 }
815
816 if ( !rootLayerName.isEmpty() )
817 {
818 QDomElement layerParentNameElem = doc.createElement( QStringLiteral( "Name" ) );
819 QDomText layerParentNameText = doc.createTextNode( rootLayerName );
820 layerParentNameElem.appendChild( layerParentNameText );
821 layerParentElem.appendChild( layerParentNameElem );
822 }
823
824 // Root Layer title
825 QDomElement layerParentTitleElem = doc.createElement( QStringLiteral( "Title" ) );
826 QDomText layerParentTitleText = doc.createTextNode( QgsServerProjectUtils::owsServiceTitle( *project ) );
827 layerParentTitleElem.appendChild( layerParentTitleText );
828 layerParentElem.appendChild( layerParentTitleElem );
829
830 // Root Layer abstract
831 const QString rootLayerAbstract = QgsServerProjectUtils::owsServiceAbstract( *project );
832 if ( !rootLayerAbstract.isEmpty() )
833 {
834 QDomElement layerParentAbstElem = doc.createElement( QStringLiteral( "Abstract" ) );
835 QDomText layerParentAbstText = doc.createCDATASection( rootLayerAbstract );
836 layerParentAbstElem.appendChild( layerParentAbstText );
837 layerParentElem.appendChild( layerParentAbstElem );
838 }
839
840 // Keyword list
841 addKeywordListElement( project, doc, layerParentElem );
842
843 // Root Layer tree name
844 if ( projectSettings )
845 {
846 QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
847 QDomText treeNameText = doc.createTextNode( project->title() );
848 treeNameElem.appendChild( treeNameText );
849 layerParentElem.appendChild( treeNameElem );
850 }
851
852 if ( hasQueryableChildren( projectLayerTreeRoot, QgsServerProjectUtils::wmsRestrictedLayers( *project ) ) )
853 {
854 layerParentElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
855 }
856 else
857 {
858 layerParentElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
859 }
860
861 appendLayersFromTreeGroup( doc, layerParentElem, serverIface, project, request, projectLayerTreeRoot, projectSettings );
862
863 combineExtentAndCrsOfGroupChildren( doc, layerParentElem, project, true );
864
865 return layerParentElem;
866 }
867
868 namespace
869 {
870
871 void appendLayersFromTreeGroup( QDomDocument &doc,
872 QDomElement &parentLayer,
873 QgsServerInterface *serverIface,
874 const QgsProject *project,
875 const QgsWmsRequest &request,
876 const QgsLayerTreeGroup *layerTreeGroup,
877 bool projectSettings )
878 {
879 const QString version = request.wmsParameters().version();
880
881 bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
882 bool siaFormat = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
883 const QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
884
885 QList< QgsLayerTreeNode * > layerTreeGroupChildren = layerTreeGroup->children();
886 for ( int i = 0; i < layerTreeGroupChildren.size(); ++i )
887 {
888 QgsLayerTreeNode *treeNode = layerTreeGroupChildren.at( i );
889 QDomElement layerElem = doc.createElement( QStringLiteral( "Layer" ) );
890
891 if ( projectSettings )
892 {
893 layerElem.setAttribute( QStringLiteral( "visible" ), treeNode->isVisible() );
894 layerElem.setAttribute( QStringLiteral( "visibilityChecked" ), treeNode->itemVisibilityChecked() );
895 layerElem.setAttribute( QStringLiteral( "expanded" ), treeNode->isExpanded() );
896 }
897
898 if ( treeNode->nodeType() == QgsLayerTreeNode::NodeGroup )
899 {
900 QgsLayerTreeGroup *treeGroupChild = static_cast<QgsLayerTreeGroup *>( treeNode );
901
902 QString name = treeGroupChild->name();
903 if ( restrictedLayers.contains( name ) ) //unpublished group
904 {
905 continue;
906 }
907
908 if ( projectSettings )
909 {
910 layerElem.setAttribute( QStringLiteral( "mutuallyExclusive" ), treeGroupChild->isMutuallyExclusive() );
911 }
912
913 QString shortName = treeGroupChild->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
914 QString title = treeGroupChild->customProperty( QStringLiteral( "wmsTitle" ) ).toString();
915
916 QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
917 QDomText nameText;
918 if ( !shortName.isEmpty() )
919 nameText = doc.createTextNode( shortName );
920 else
921 nameText = doc.createTextNode( name );
922 nameElem.appendChild( nameText );
923 layerElem.appendChild( nameElem );
924
925 QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) );
926 QDomText titleText;
927 if ( !title.isEmpty() )
928 titleText = doc.createTextNode( title );
929 else
930 titleText = doc.createTextNode( name );
931 titleElem.appendChild( titleText );
932 layerElem.appendChild( titleElem );
933
934 QString abstract = treeGroupChild->customProperty( QStringLiteral( "wmsAbstract" ) ).toString();
935 if ( !abstract.isEmpty() )
936 {
937 QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) );
938 QDomText abstractText = doc.createTextNode( abstract );
939 abstractElem.appendChild( abstractText );
940 layerElem.appendChild( abstractElem );
941 }
942
943 // Layer tree name
944 if ( projectSettings )
945 {
946 QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
947 QDomText treeNameText = doc.createTextNode( name );
948 treeNameElem.appendChild( treeNameText );
949 layerElem.appendChild( treeNameElem );
950 }
951
952 // Set queryable if any of the children are
953 if ( hasQueryableChildren( treeNode, restrictedLayers ) )
954 {
955 layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
956 }
957 else
958 {
959 layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
960 }
961
962 appendLayersFromTreeGroup( doc, layerElem, serverIface, project, request, treeGroupChild, projectSettings );
963
964 combineExtentAndCrsOfGroupChildren( doc, layerElem, project );
965 }
966 else
967 {
968 QgsLayerTreeLayer *treeLayer = static_cast<QgsLayerTreeLayer *>( treeNode );
969 QgsMapLayer *l = treeLayer->layer();
970 if ( !l || restrictedLayers.contains( l->name() ) ) //unpublished layer
971 {
972 continue;
973 }
974
975#ifdef HAVE_SERVER_PYTHON_PLUGINS
976 QgsAccessControl *accessControl = serverIface->accessControls();
977 if ( accessControl && !accessControl->layerReadPermission( l ) )
978 {
979 continue;
980 }
981#endif
982 QString wmsName = l->name();
983 if ( useLayerIds )
984 {
985 wmsName = l->id();
986 }
987 else if ( !l->shortName().isEmpty() )
988 {
989 wmsName = l->shortName();
990 }
991
992 // queryable layer
993 if ( !l->flags().testFlag( QgsMapLayer::Identifiable ) )
994 {
995 layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
996 }
997 else
998 {
999 layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
1000 }
1001
1002 QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
1003 QDomText nameText = doc.createTextNode( wmsName );
1004 nameElem.appendChild( nameText );
1005 layerElem.appendChild( nameElem );
1006
1007 QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) );
1008 QString title = l->title();
1009 if ( title.isEmpty() )
1010 {
1011 title = l->name();
1012 }
1013 QDomText titleText = doc.createTextNode( title );
1014 titleElem.appendChild( titleText );
1015 layerElem.appendChild( titleElem );
1016
1017 QString abstract = l->abstract();
1018 if ( !abstract.isEmpty() )
1019 {
1020 QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) );
1021 QDomText abstractText = doc.createTextNode( abstract );
1022 abstractElem.appendChild( abstractText );
1023 layerElem.appendChild( abstractElem );
1024 }
1025
1026 //keyword list
1027 if ( !l->keywordList().isEmpty() )
1028 {
1029 QStringList keywordStringList = l->keywordList().split( ',' );
1030
1031 QDomElement keywordListElem = doc.createElement( QStringLiteral( "KeywordList" ) );
1032 for ( int i = 0; i < keywordStringList.size(); ++i )
1033 {
1034 QDomElement keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
1035 QDomText keywordText = doc.createTextNode( keywordStringList.at( i ).trimmed() );
1036 keywordElem.appendChild( keywordText );
1037 if ( siaFormat )
1038 {
1039 keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "SIA_Geo405" ) );
1040 }
1041 keywordListElem.appendChild( keywordElem );
1042 }
1043 layerElem.appendChild( keywordListElem );
1044 }
1045
1046 //vector layer without geometry
1047 bool geometryLayer = true;
1048 if ( l->type() == QgsMapLayerType::VectorLayer )
1049 {
1050 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( l );
1051 if ( vLayer )
1052 {
1053 if ( vLayer->wkbType() == QgsWkbTypes::NoGeometry )
1054 {
1055 geometryLayer = false;
1056 }
1057 }
1058 }
1059
1060 //CRS
1061 if ( geometryLayer )
1062 {
1063 QStringList crsList;
1064 crsList << l->crs().authid();
1065 QStringList outputCrsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
1066 appendCrsElementsToLayer( doc, layerElem, crsList, outputCrsList );
1067
1068 //Ex_GeographicBoundingBox
1069 QgsRectangle extent = l->extent(); // layer extent by default
1070 QgsRectangle wgs84Extent;
1071 if ( extent.isEmpty() )
1072 {
1073 // if the extent is empty (not only Null), use the wms extent
1074 // defined in the project...
1075 extent = QgsServerProjectUtils::wmsExtent( *project );
1076 if ( extent.isNull() )
1077 {
1078 // or the CRS extent otherwise
1079 extent = l->crs().bounds();
1080 }
1081 else if ( l->crs() != project->crs() )
1082 {
1083 // If CRS is different transform it to layer's CRS
1084 try
1085 {
1086 QgsCoordinateTransform ct( project->crs(), l->crs(), project->transformContext() );
1087 extent = ct.transform( extent );
1088 }
1089 catch ( QgsCsException &cse )
1090 {
1091 QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent for layer %1: %2" ).arg( l->name() ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1092 continue;
1093 }
1094 }
1095 }
1096 else
1097 {
1098 // Get the wgs84 extent from layer
1099 wgs84Extent = l->wgs84Extent();
1100 }
1101
1102 appendLayerBoundingBoxes( doc, layerElem, extent, l->crs(), crsList, outputCrsList, project, wgs84Extent );
1103 }
1104
1105 // add details about supported styles of the layer
1106 appendLayerStyles( doc, layerElem, l, project, request, serverIface->serverSettings() );
1107
1108 //min/max scale denominatorScaleBasedVisibility
1109 if ( l->hasScaleBasedVisibility() )
1110 {
1111 if ( version == QLatin1String( "1.1.1" ) )
1112 {
1113 double OGC_PX_M = 0.00028; // OGC reference pixel size in meter, also used by qgis
1114 double SCALE_TO_SCALEHINT = OGC_PX_M * M_SQRT2;
1115
1116 QDomElement scaleHintElem = doc.createElement( QStringLiteral( "ScaleHint" ) );
1117 scaleHintElem.setAttribute( QStringLiteral( "min" ), QString::number( l->maximumScale() * SCALE_TO_SCALEHINT ) );
1118 scaleHintElem.setAttribute( QStringLiteral( "max" ), QString::number( l->minimumScale() * SCALE_TO_SCALEHINT ) );
1119 layerElem.appendChild( scaleHintElem );
1120 }
1121 else
1122 {
1123 QString minScaleString = QString::number( l->maximumScale() );
1124 QDomElement minScaleElem = doc.createElement( QStringLiteral( "MinScaleDenominator" ) );
1125 QDomText minScaleText = doc.createTextNode( minScaleString );
1126 minScaleElem.appendChild( minScaleText );
1127 layerElem.appendChild( minScaleElem );
1128
1129 QString maxScaleString = QString::number( l->minimumScale() );
1130 QDomElement maxScaleElem = doc.createElement( QStringLiteral( "MaxScaleDenominator" ) );
1131 QDomText maxScaleText = doc.createTextNode( maxScaleString );
1132 maxScaleElem.appendChild( maxScaleText );
1133 layerElem.appendChild( maxScaleElem );
1134 }
1135 }
1136
1137 // layer data URL
1138 QString dataUrl = l->dataUrl();
1139 if ( !dataUrl.isEmpty() )
1140 {
1141 QDomElement dataUrlElem = doc.createElement( QStringLiteral( "DataURL" ) );
1142 QDomElement dataUrlFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1143 QString dataUrlFormat = l->dataUrlFormat();
1144 QDomText dataUrlFormatText = doc.createTextNode( dataUrlFormat );
1145 dataUrlFormatElem.appendChild( dataUrlFormatText );
1146 dataUrlElem.appendChild( dataUrlFormatElem );
1147 QDomElement dataORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1148 dataORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1149 dataORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1150 dataORElem.setAttribute( QStringLiteral( "xlink:href" ), dataUrl );
1151 dataUrlElem.appendChild( dataORElem );
1152 layerElem.appendChild( dataUrlElem );
1153 }
1154
1155 // layer attribution
1156 QString attribution = l->attribution();
1157 if ( !attribution.isEmpty() )
1158 {
1159 QDomElement attribElem = doc.createElement( QStringLiteral( "Attribution" ) );
1160 QDomElement attribTitleElem = doc.createElement( QStringLiteral( "Title" ) );
1161 QDomText attribText = doc.createTextNode( attribution );
1162 attribTitleElem.appendChild( attribText );
1163 attribElem.appendChild( attribTitleElem );
1164 QString attributionUrl = l->attributionUrl();
1165 if ( !attributionUrl.isEmpty() )
1166 {
1167 QDomElement attribORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1168 attribORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1169 attribORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1170 attribORElem.setAttribute( QStringLiteral( "xlink:href" ), attributionUrl );
1171 attribElem.appendChild( attribORElem );
1172 }
1173 layerElem.appendChild( attribElem );
1174 }
1175
1176 // layer metadata URL
1177 const QList<QgsMapLayerServerProperties::MetadataUrl> urls = l->serverProperties()->metadataUrls();
1178 for ( const QgsMapLayerServerProperties::MetadataUrl &url : urls )
1179 {
1180 QDomElement metaUrlElem = doc.createElement( QStringLiteral( "MetadataURL" ) );
1181 QString metadataUrlType = url.type;
1182 if ( version == QLatin1String( "1.1.1" ) )
1183 {
1184 metaUrlElem.setAttribute( QStringLiteral( "type" ), metadataUrlType );
1185 }
1186 else if ( metadataUrlType == QLatin1String( "FGDC" ) )
1187 {
1188 metaUrlElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "FGDC:1998" ) );
1189 }
1190 else if ( metadataUrlType == QLatin1String( "TC211" ) )
1191 {
1192 metaUrlElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "ISO19115:2003" ) );
1193 }
1194 else
1195 {
1196 metaUrlElem.setAttribute( QStringLiteral( "type" ), metadataUrlType );
1197 }
1198 QString metadataUrlFormat = url.format;
1199 if ( !metadataUrlFormat.isEmpty() )
1200 {
1201 QDomElement metaUrlFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1202 QDomText metaUrlFormatText = doc.createTextNode( metadataUrlFormat );
1203 metaUrlFormatElem.appendChild( metaUrlFormatText );
1204 metaUrlElem.appendChild( metaUrlFormatElem );
1205 }
1206 QDomElement metaUrlORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1207 metaUrlORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1208 metaUrlORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1209 metaUrlORElem.setAttribute( QStringLiteral( "xlink:href" ), url.url );
1210 metaUrlElem.appendChild( metaUrlORElem );
1211 layerElem.appendChild( metaUrlElem );
1212 }
1213
1214 bool timeDimensionAdded { false };
1215
1216 // Add dimensions
1217 if ( l->type() == QgsMapLayerType::VectorLayer )
1218 {
1219 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( l );
1220 QgsMapLayerServerProperties *serverProperties = static_cast<QgsMapLayerServerProperties *>( vl->serverProperties() );
1221 const QList<QgsMapLayerServerProperties::WmsDimensionInfo> wmsDims = serverProperties->wmsDimensions();
1222 for ( const QgsMapLayerServerProperties::WmsDimensionInfo &dim : wmsDims )
1223 {
1224
1225 int fieldIndex = vl->fields().indexOf( dim.fieldName );
1226 // Check field index
1227 if ( fieldIndex == -1 )
1228 {
1229 continue;
1230 }
1231 // get unique values
1232 QSet<QVariant> uniqueValues = vl->uniqueValues( fieldIndex );
1233
1234 // get unique values from endfield name if define
1235 if ( !dim.endFieldName.isEmpty() )
1236 {
1237 int endFieldIndex = vl->fields().indexOf( dim.endFieldName );
1238 // Check end field index
1239 if ( endFieldIndex == -1 )
1240 {
1241 continue;
1242 }
1243 uniqueValues.unite( vl->uniqueValues( endFieldIndex ) );
1244 }
1245 // sort unique values
1246 QList<QVariant> values = qgis::setToList( uniqueValues );
1247 std::sort( values.begin(), values.end() );
1248
1249 QDomElement dimElem = doc.createElement( QStringLiteral( "Dimension" ) );
1250 dimElem.setAttribute( QStringLiteral( "name" ), dim.name );
1251
1252 if ( dim.name.toUpper() == QLatin1String( "TIME" ) )
1253 {
1254 timeDimensionAdded = true;
1255 }
1256
1257 if ( !dim.units.isEmpty() )
1258 {
1259 dimElem.setAttribute( QStringLiteral( "units" ), dim.units );
1260 }
1261 if ( !dim.unitSymbol.isEmpty() )
1262 {
1263 dimElem.setAttribute( QStringLiteral( "unitSymbol" ), dim.unitSymbol );
1264 }
1265 if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MinValue )
1266 {
1267 dimElem.setAttribute( QStringLiteral( "default" ), values.first().toString() );
1268 }
1269 else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MaxValue )
1270 {
1271 dimElem.setAttribute( QStringLiteral( "default" ), values.last().toString() );
1272 }
1273 else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::ReferenceValue )
1274 {
1275 dimElem.setAttribute( QStringLiteral( "default" ), dim.referenceValue.toString() );
1276 }
1277 dimElem.setAttribute( QStringLiteral( "multipleValues" ), QStringLiteral( "1" ) );
1278 dimElem.setAttribute( QStringLiteral( "nearestValue" ), QStringLiteral( "0" ) );
1279 if ( projectSettings )
1280 {
1281 dimElem.setAttribute( QStringLiteral( "fieldName" ), dim.fieldName );
1282 dimElem.setAttribute( QStringLiteral( "endFieldName" ), dim.endFieldName );
1283 }
1284 // values list
1285 QStringList strValues;
1286 for ( const QVariant &v : values )
1287 {
1288 strValues << v.toString();
1289 }
1290 QDomText dimValuesText = doc.createTextNode( strValues.join( QLatin1String( ", " ) ) );
1291 dimElem.appendChild( dimValuesText );
1292 layerElem.appendChild( dimElem );
1293 }
1294 }
1295
1296 // Add WMS time dimension if not already added
1297 if ( ! timeDimensionAdded
1298 && l->temporalProperties()
1299 && l->temporalProperties()->isActive() )
1300 {
1301
1302 QDomElement dimElem = doc.createElement( QStringLiteral( "Dimension" ) );
1303 dimElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "TIME" ) );
1304 dimElem.setAttribute( QStringLiteral( "units" ), QStringLiteral( "ISO8601" ) );
1305
1306 // TODO: set "default" (reference value)
1307
1308 // Add all values
1309 const QList<QgsDateTimeRange> allRanges { l->temporalProperties()->allTemporalRanges( l ) };
1310
1311 // Apparently, for vectors allTemporalRanges is always empty :/
1312 // there is no way to know the type of range or the individual instants
1313
1314 bool isDateList { true };
1315 bool isInstantList { true };
1316
1317 QList<QDateTime> values;
1318 for ( const auto &r : std::as_const( allRanges ) )
1319 {
1320 if ( r.isInstant() )
1321 {
1322 if ( r.begin().time() != QTime( 0, 0, 0, 0 ) )
1323 {
1324 isDateList = false;
1325 }
1326 values.append( r.begin() );
1327 }
1328 else
1329 {
1330 isInstantList = false;
1331 break;
1332 }
1333 }
1334
1335 // Only list individual values for list of instants,
1336 // otherwise only the extent will be shown
1337 if ( isInstantList )
1338 {
1339 // values list
1340 QStringList strValues;
1341 for ( const auto &v : values )
1342 {
1343 if ( isDateList )
1344 {
1345 strValues << v.date().toString( Qt::DateFormat::ISODate );
1346 }
1347 else
1348 {
1349 strValues << v.toString( Qt::DateFormat::ISODate );
1350 }
1351 }
1352 QDomText dimValuesText = doc.createTextNode( strValues.join( QChar( ',' ) ) );
1353 dimElem.appendChild( dimValuesText );
1354 }
1355
1356 layerElem.appendChild( dimElem );
1357
1358 QDomElement timeExtentElem = doc.createElement( QStringLiteral( "Extent" ) );
1359 timeExtentElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "TIME" ) );
1360
1361 const QgsDateTimeRange timeExtent { l->temporalProperties()->calculateTemporalExtent( l ) };
1362 QString extent;
1363 if ( isDateList )
1364 {
1365 extent = QStringLiteral( "%1/%2" ).arg( timeExtent.begin().date().toString( Qt::DateFormat::ISODate ), timeExtent.end().date().toString( Qt::DateFormat::ISODate ) );
1366 }
1367 else
1368 {
1369 extent = QStringLiteral( "%1/%2" ).arg( timeExtent.begin().toString( Qt::DateFormat::ISODate ), timeExtent.end().toString( Qt::DateFormat::ISODate ) );
1370 }
1371 QDomText extentValueText = doc.createTextNode( extent );
1372 timeExtentElem.appendChild( extentValueText );
1373 layerElem.appendChild( timeExtentElem );
1374
1375 }
1376
1377 if ( projectSettings )
1378 {
1379 appendLayerProjectSettings( doc, layerElem, l );
1380 }
1381 }
1382
1383 parentLayer.appendChild( layerElem );
1384 }
1385 }
1386
1387 void appendLayerStyles( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer,
1388 const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *settings )
1389 {
1390 // Get service URL
1391 QUrl href = serviceUrl( request, project, *settings );
1392
1393 //href needs to be a prefix
1394 QString hrefString = href.toString();
1395 hrefString.append( href.hasQuery() ? "&" : "?" );
1396 for ( const QString &styleName : currentLayer->styleManager()->styles() )
1397 {
1398 QDomElement styleElem = doc.createElement( QStringLiteral( "Style" ) );
1399 QDomElement styleNameElem = doc.createElement( QStringLiteral( "Name" ) );
1400 QDomText styleNameText = doc.createTextNode( styleName );
1401 styleNameElem.appendChild( styleNameText );
1402 QDomElement styleTitleElem = doc.createElement( QStringLiteral( "Title" ) );
1403 QDomText styleTitleText = doc.createTextNode( styleName );
1404 styleTitleElem.appendChild( styleTitleText );
1405 styleElem.appendChild( styleNameElem );
1406 styleElem.appendChild( styleTitleElem );
1407
1408 // QString LegendURL for explicit layerbased GetLegendGraphic request
1409 QDomElement getLayerLegendGraphicElem = doc.createElement( QStringLiteral( "LegendURL" ) );
1410
1411 QString customHrefString = currentLayer->legendUrl();
1412
1413 QStringList getLayerLegendGraphicFormats;
1414 if ( !customHrefString.isEmpty() )
1415 {
1416 getLayerLegendGraphicFormats << currentLayer->legendUrlFormat();
1417 }
1418 else
1419 {
1420 getLayerLegendGraphicFormats << QStringLiteral( "image/png" ); // << "jpeg" << "image/jpeg"
1421 }
1422
1423 for ( int i = 0; i < getLayerLegendGraphicFormats.size(); ++i )
1424 {
1425 QDomElement getLayerLegendGraphicFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1426 QString getLayerLegendGraphicFormat = getLayerLegendGraphicFormats[i];
1427 QDomText getLayerLegendGraphicFormatText = doc.createTextNode( getLayerLegendGraphicFormat );
1428 getLayerLegendGraphicFormatElem.appendChild( getLayerLegendGraphicFormatText );
1429 getLayerLegendGraphicElem.appendChild( getLayerLegendGraphicFormatElem );
1430 }
1431
1432 // no parameters on custom hrefUrl, because should link directly to graphic
1433 if ( customHrefString.isEmpty() )
1434 {
1435 QString layerName = currentLayer->name();
1436 if ( QgsServerProjectUtils::wmsUseLayerIds( *project ) )
1437 layerName = currentLayer->id();
1438 else if ( !currentLayer->shortName().isEmpty() )
1439 layerName = currentLayer->shortName();
1440 QUrl mapUrl( hrefString );
1441 QUrlQuery mapUrlQuery( mapUrl.query() );
1442 mapUrlQuery.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WMS" ) );
1443 mapUrlQuery.addQueryItem( QStringLiteral( "VERSION" ), request.wmsParameters().version() );
1444 mapUrlQuery.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetLegendGraphic" ) );
1445 mapUrlQuery.addQueryItem( QStringLiteral( "LAYER" ), layerName );
1446 mapUrlQuery.addQueryItem( QStringLiteral( "FORMAT" ), QStringLiteral( "image/png" ) );
1447 mapUrlQuery.addQueryItem( QStringLiteral( "STYLE" ), styleNameText.data() );
1448 if ( request.wmsParameters().version() == QLatin1String( "1.3.0" ) )
1449 {
1450 mapUrlQuery.addQueryItem( QStringLiteral( "SLD_VERSION" ), QStringLiteral( "1.1.0" ) );
1451 }
1452 mapUrl.setQuery( mapUrlQuery );
1453 customHrefString = mapUrl.toString();
1454 }
1455
1456 QDomElement getLayerLegendGraphicORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1457 getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1458 getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1459 getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xlink:href" ), customHrefString );
1460 getLayerLegendGraphicElem.appendChild( getLayerLegendGraphicORElem );
1461 styleElem.appendChild( getLayerLegendGraphicElem );
1462
1463 layerElem.appendChild( styleElem );
1464 }
1465 }
1466
1467 void appendCrsElementsToLayer( QDomDocument &doc, QDomElement &layerElement,
1468 const QStringList &crsList, const QStringList &constrainedCrsList )
1469 {
1470 if ( layerElement.isNull() )
1471 {
1472 return;
1473 }
1474
1475 const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1476
1477 //insert the CRS elements after the title element to be in accordance with the WMS 1.3 specification
1478 QDomElement titleElement = layerElement.firstChildElement( QStringLiteral( "Title" ) );
1479 QDomElement abstractElement = layerElement.firstChildElement( QStringLiteral( "Abstract" ) );
1480 QDomElement keywordListElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1481 QDomElement CRSPrecedingElement = !keywordListElement.isNull() ? keywordListElement : !abstractElement.isNull() ? abstractElement : titleElement;
1482
1483 if ( CRSPrecedingElement.isNull() )
1484 {
1485 // keyword list element is never empty
1486 const QDomElement keyElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1487 CRSPrecedingElement = keyElement;
1488 }
1489
1490 //In case the number of advertised CRS is constrained
1491 if ( !constrainedCrsList.isEmpty() )
1492 {
1493 for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1494 {
1495 appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, constrainedCrsList.at( i ) );
1496 }
1497 }
1498 else //no crs constraint
1499 {
1500 for ( const QString &crs : crsList )
1501 {
1502 appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, crs );
1503 }
1504 }
1505
1506 // Support for CRS:84 is mandatory (equals EPSG:4326 with reversed axis)
1507 // https://github.com/opengeospatial/ets-wms13/blob/47155399c09b200cb21382874fdb21d5fae4ab6e/src/site/markdown/index.md
1508 if ( version == QLatin1String( "1.3.0" ) )
1509 {
1510 appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, QString( "CRS:84" ) );
1511 }
1512 }
1513
1514 void appendCrsElementToLayer( QDomDocument &doc, QDomElement &layerElement, const QDomElement &precedingElement,
1515 const QString &crsText )
1516 {
1517 if ( crsText.isEmpty() )
1518 return;
1519 const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1520 QDomElement crsElement = doc.createElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1521 QDomText crsTextNode = doc.createTextNode( crsText );
1522 crsElement.appendChild( crsTextNode );
1523 layerElement.insertAfter( crsElement, precedingElement );
1524 }
1525
1526 void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent,
1527 const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList,
1528 const QStringList &constrainedCrsList, const QgsProject *project,
1529 const QgsRectangle &lGeoExtent )
1530 {
1531 if ( layerElem.isNull() )
1532 {
1533 return;
1534 }
1535
1536 QgsRectangle layerExtent = lExtent;
1537 if ( qgsDoubleNear( layerExtent.xMinimum(), layerExtent.xMaximum() ) || qgsDoubleNear( layerExtent.yMinimum(), layerExtent.yMaximum() ) )
1538 {
1539 //layer bbox cannot be empty
1540 layerExtent.grow( 0.000001 );
1541 }
1542
1543 QgsRectangle wgs84BoundingRect = lGeoExtent;
1544 if ( wgs84BoundingRect.isNull() )
1545 {
1547
1548 //transform the layers native CRS into WGS84
1549 if ( !layerExtent.isNull() )
1550 {
1551 QgsCoordinateTransform exGeoTransform( layerCRS, wgs84, project );
1552 try
1553 {
1554 wgs84BoundingRect = exGeoTransform.transformBoundingBox( layerExtent );
1555 }
1556 catch ( const QgsCsException &cse )
1557 {
1558 QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1559 wgs84BoundingRect = QgsRectangle();
1560 }
1561 }
1562 }
1563
1564 if ( qgsDoubleNear( wgs84BoundingRect.xMinimum(), wgs84BoundingRect.xMaximum() ) || qgsDoubleNear( wgs84BoundingRect.yMinimum(), wgs84BoundingRect.yMaximum() ) )
1565 {
1566 //layer bbox cannot be empty
1567 wgs84BoundingRect.grow( 0.000001 );
1568 }
1569
1570 //Ex_GeographicBoundingBox
1571 QDomElement ExGeoBBoxElement;
1572 const int wgs84precision = 6;
1573 const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1574 if ( version == QLatin1String( "1.1.1" ) ) // WMS Version 1.1.1
1575 {
1576 ExGeoBBoxElement = doc.createElement( QStringLiteral( "LatLonBoundingBox" ) );
1577 ExGeoBBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1578 ExGeoBBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1579 ExGeoBBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1580 ExGeoBBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1581 }
1582 else // WMS Version 1.3.0
1583 {
1584 ExGeoBBoxElement = doc.createElement( QStringLiteral( "EX_GeographicBoundingBox" ) );
1585 QDomElement wBoundLongitudeElement = doc.createElement( QStringLiteral( "westBoundLongitude" ) );
1586 QDomText wBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1587 wBoundLongitudeElement.appendChild( wBoundLongitudeText );
1588 ExGeoBBoxElement.appendChild( wBoundLongitudeElement );
1589 QDomElement eBoundLongitudeElement = doc.createElement( QStringLiteral( "eastBoundLongitude" ) );
1590 QDomText eBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1591 eBoundLongitudeElement.appendChild( eBoundLongitudeText );
1592 ExGeoBBoxElement.appendChild( eBoundLongitudeElement );
1593 QDomElement sBoundLatitudeElement = doc.createElement( QStringLiteral( "southBoundLatitude" ) );
1594 QDomText sBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1595 sBoundLatitudeElement.appendChild( sBoundLatitudeText );
1596 ExGeoBBoxElement.appendChild( sBoundLatitudeElement );
1597 QDomElement nBoundLatitudeElement = doc.createElement( QStringLiteral( "northBoundLatitude" ) );
1598 QDomText nBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1599 nBoundLatitudeElement.appendChild( nBoundLatitudeText );
1600 ExGeoBBoxElement.appendChild( nBoundLatitudeElement );
1601 }
1602
1603 if ( !wgs84BoundingRect.isNull() ) //LatLonBoundingBox / Ex_GeographicBounding box is optional
1604 {
1605 QDomElement lastCRSElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1606 if ( !lastCRSElem.isNull() )
1607 {
1608 layerElem.insertAfter( ExGeoBBoxElement, lastCRSElem );
1609 }
1610 else
1611 {
1612 layerElem.appendChild( ExGeoBBoxElement );
1613 }
1614 }
1615
1616 //In case the number of advertised CRS is constrained
1617 if ( !constrainedCrsList.isEmpty() )
1618 {
1619 for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1620 {
1621 appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, constrainedCrsList.at( i ), project );
1622 }
1623 }
1624 else //no crs constraint
1625 {
1626 for ( const QString &crs : crsList )
1627 {
1628 appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, crs, project );
1629 }
1630 }
1631 }
1632
1633
1634 void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent,
1635 const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText,
1636 const QgsProject *project )
1637 {
1638 if ( layerElem.isNull() )
1639 {
1640 return;
1641 }
1642
1643 if ( crsText.isEmpty() )
1644 {
1645 return;
1646 }
1647
1648 const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1649
1651
1652 //transform the layers native CRS into CRS
1653 QgsRectangle crsExtent;
1654 if ( !layerExtent.isNull() )
1655 {
1656 QgsCoordinateTransform crsTransform( layerCRS, crs, project );
1657 try
1658 {
1659 crsExtent = crsTransform.transformBoundingBox( layerExtent );
1660 }
1661 catch ( QgsCsException &cse )
1662 {
1663 QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1664 return;
1665 }
1666 }
1667
1668 if ( crsExtent.isNull() )
1669 {
1670 return;
1671 }
1672
1673 int precision = 3;
1674 if ( crs.isGeographic() )
1675 {
1676 precision = 6;
1677 }
1678
1679 //BoundingBox element
1680 QDomElement bBoxElement = doc.createElement( QStringLiteral( "BoundingBox" ) );
1681 if ( crs.isValid() )
1682 {
1683 bBoxElement.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", crs.authid() );
1684 }
1685
1686 if ( version != QLatin1String( "1.1.1" ) && crs.hasAxisInverted() )
1687 {
1688 crsExtent.invert();
1689 }
1690
1691 bBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.xMinimum(), precision ), precision ) );
1692 bBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.yMinimum(), precision ), precision ) );
1693 bBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.xMaximum(), precision ), precision ) );
1694 bBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.yMaximum(), precision ), precision ) );
1695
1696 QDomElement lastBBoxElem = layerElem.lastChildElement( QStringLiteral( "BoundingBox" ) );
1697 if ( !lastBBoxElem.isNull() )
1698 {
1699 layerElem.insertAfter( bBoxElement, lastBBoxElem );
1700 }
1701 else
1702 {
1703 lastBBoxElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "LatLonBoundingBox" : "EX_GeographicBoundingBox" );
1704 if ( !lastBBoxElem.isNull() )
1705 {
1706 layerElem.insertAfter( bBoxElement, lastBBoxElem );
1707 }
1708 else
1709 {
1710 layerElem.appendChild( bBoxElement );
1711 }
1712 }
1713 }
1714
1715 QgsRectangle layerBoundingBoxInProjectCrs( const QDomDocument &doc, const QDomElement &layerElem,
1716 const QgsProject *project )
1717 {
1718 QgsRectangle BBox;
1719 if ( layerElem.isNull() )
1720 {
1721 return BBox;
1722 }
1723
1724 //read box coordinates and layer auth. id
1725 QDomElement boundingBoxElem = layerElem.firstChildElement( QStringLiteral( "BoundingBox" ) );
1726 if ( boundingBoxElem.isNull() )
1727 {
1728 return BBox;
1729 }
1730
1731 double minx, miny, maxx, maxy;
1732 bool conversionOk;
1733 minx = boundingBoxElem.attribute( QStringLiteral( "minx" ) ).toDouble( &conversionOk );
1734 if ( !conversionOk )
1735 {
1736 return BBox;
1737 }
1738 miny = boundingBoxElem.attribute( QStringLiteral( "miny" ) ).toDouble( &conversionOk );
1739 if ( !conversionOk )
1740 {
1741 return BBox;
1742 }
1743 maxx = boundingBoxElem.attribute( QStringLiteral( "maxx" ) ).toDouble( &conversionOk );
1744 if ( !conversionOk )
1745 {
1746 return BBox;
1747 }
1748 maxy = boundingBoxElem.attribute( QStringLiteral( "maxy" ) ).toDouble( &conversionOk );
1749 if ( !conversionOk )
1750 {
1751 return BBox;
1752 }
1753
1754
1755 const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1756
1757 //create layer crs
1758 QgsCoordinateReferenceSystem layerCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( boundingBoxElem.attribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" ) );
1759 if ( !layerCrs.isValid() )
1760 {
1761 return BBox;
1762 }
1763
1764 BBox.setXMinimum( minx );
1765 BBox.setXMaximum( maxx );
1766 BBox.setYMinimum( miny );
1767 BBox.setYMaximum( maxy );
1768
1769 if ( version != QLatin1String( "1.1.1" ) && layerCrs.hasAxisInverted() )
1770 {
1771 BBox.invert();
1772 }
1773
1774 //get project crs
1775 QgsCoordinateTransform t( layerCrs, project->crs(), project );
1776
1777 //transform
1778 try
1779 {
1780 BBox = t.transformBoundingBox( BBox );
1781 }
1782 catch ( const QgsCsException &cse )
1783 {
1784 QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1785 BBox = QgsRectangle();
1786 }
1787
1788 return BBox;
1789 }
1790
1791 bool crsSetFromLayerElement( const QDomElement &layerElement, QSet<QString> &crsSet )
1792 {
1793 if ( layerElement.isNull() )
1794 {
1795 return false;
1796 }
1797
1798 crsSet.clear();
1799
1800 QDomNodeList crsNodeList;
1801 crsNodeList = layerElement.elementsByTagName( QStringLiteral( "CRS" ) ); // WMS 1.3.0
1802 for ( int i = 0; i < crsNodeList.size(); ++i )
1803 {
1804 crsSet.insert( crsNodeList.at( i ).toElement().text() );
1805 }
1806
1807 crsNodeList = layerElement.elementsByTagName( QStringLiteral( "SRS" ) ); // WMS 1.1.1
1808 for ( int i = 0; i < crsNodeList.size(); ++i )
1809 {
1810 crsSet.insert( crsNodeList.at( i ).toElement().text() );
1811 }
1812
1813 return true;
1814 }
1815
1816 void combineExtentAndCrsOfGroupChildren( QDomDocument &doc, QDomElement &groupElem, const QgsProject *project,
1817 bool considerMapExtent )
1818 {
1819 QgsRectangle combinedBBox;
1820 QSet<QString> combinedCRSSet;
1821 bool firstBBox = true;
1822 bool firstCRSSet = true;
1823
1824 QDomNodeList layerChildren = groupElem.childNodes();
1825 for ( int j = 0; j < layerChildren.size(); ++j )
1826 {
1827 QDomElement childElem = layerChildren.at( j ).toElement();
1828
1829 if ( childElem.tagName() != QLatin1String( "Layer" ) )
1830 continue;
1831
1832 QgsRectangle bbox = layerBoundingBoxInProjectCrs( doc, childElem, project );
1833 if ( bbox.isNull() )
1834 {
1835 continue;
1836 }
1837
1838 if ( !bbox.isEmpty() )
1839 {
1840 if ( firstBBox )
1841 {
1842 combinedBBox = bbox;
1843 firstBBox = false;
1844 }
1845 else
1846 {
1847 combinedBBox.combineExtentWith( bbox );
1848 }
1849 }
1850
1851 //combine crs set
1852 QSet<QString> crsSet;
1853 if ( crsSetFromLayerElement( childElem, crsSet ) )
1854 {
1855 if ( firstCRSSet )
1856 {
1857 combinedCRSSet = crsSet;
1858 firstCRSSet = false;
1859 }
1860 else
1861 {
1862 combinedCRSSet.intersect( crsSet );
1863 }
1864 }
1865 }
1866
1867 QStringList outputCrsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
1868 appendCrsElementsToLayer( doc, groupElem, qgis::setToList( combinedCRSSet ), outputCrsList );
1869
1870 QgsCoordinateReferenceSystem groupCRS = project->crs();
1871 if ( considerMapExtent )
1872 {
1873 QgsRectangle mapRect = QgsServerProjectUtils::wmsExtent( *project );
1874 if ( !mapRect.isEmpty() )
1875 {
1876 combinedBBox = mapRect;
1877 }
1878 }
1879 appendLayerBoundingBoxes( doc, groupElem, combinedBBox, groupCRS, qgis::setToList( combinedCRSSet ), outputCrsList, project );
1880
1881 }
1882
1883 void appendDrawingOrder( QDomDocument &doc, QDomElement &parentElem, QgsServerInterface *serverIface,
1884 const QgsProject *project )
1885 {
1886#ifdef HAVE_SERVER_PYTHON_PLUGINS
1887 QgsAccessControl *accessControl = serverIface->accessControls();
1888#else
1889 ( void )serverIface;
1890#endif
1891 bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
1892 QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
1893
1894 QStringList layerList;
1895
1896 const QgsLayerTree *projectLayerTreeRoot = project->layerTreeRoot();
1897 QList< QgsMapLayer * > projectLayerOrder = projectLayerTreeRoot->layerOrder();
1898 for ( int i = 0; i < projectLayerOrder.size(); ++i )
1899 {
1900 QgsMapLayer *l = projectLayerOrder.at( i );
1901
1902 if ( restrictedLayers.contains( l->name() ) ) //unpublished layer
1903 {
1904 continue;
1905 }
1906#ifdef HAVE_SERVER_PYTHON_PLUGINS
1907 if ( accessControl && !accessControl->layerReadPermission( l ) )
1908 {
1909 continue;
1910 }
1911#endif
1912 QString wmsName = l->name();
1913 if ( useLayerIds )
1914 {
1915 wmsName = l->id();
1916 }
1917 else if ( !l->shortName().isEmpty() )
1918 {
1919 wmsName = l->shortName();
1920 }
1921
1922 layerList << wmsName;
1923 }
1924
1925 if ( !layerList.isEmpty() )
1926 {
1927 QStringList reversedList;
1928 reversedList.reserve( layerList.size() );
1929 for ( int i = layerList.size() - 1; i >= 0; --i )
1930 reversedList << layerList[ i ];
1931
1932 QDomElement layerDrawingOrderElem = doc.createElement( QStringLiteral( "LayerDrawingOrder" ) );
1933 QDomText drawingOrderText = doc.createTextNode( reversedList.join( ',' ) );
1934 layerDrawingOrderElem.appendChild( drawingOrderText );
1935 parentElem.appendChild( layerDrawingOrderElem );
1936 }
1937 }
1938
1939 void appendLayerProjectSettings( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer )
1940 {
1941 if ( !currentLayer )
1942 {
1943 return;
1944 }
1945
1946 // Layer tree name
1947 QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
1948 QDomText treeNameText = doc.createTextNode( currentLayer->name() );
1949 treeNameElem.appendChild( treeNameText );
1950 layerElem.appendChild( treeNameElem );
1951
1952 switch ( currentLayer->type() )
1953 {
1955 {
1956 QgsVectorLayer *vLayer = static_cast<QgsVectorLayer *>( currentLayer );
1957
1958 int displayFieldIdx = -1;
1959 QString displayField = QStringLiteral( "maptip" );
1960 QgsExpression exp( vLayer->displayExpression() );
1961 if ( exp.isField() )
1962 {
1963 displayField = static_cast<const QgsExpressionNodeColumnRef *>( exp.rootNode() )->name();
1964 displayFieldIdx = vLayer->fields().lookupField( displayField );
1965 }
1966
1967 //attributes
1968 QDomElement attributesElem = doc.createElement( QStringLiteral( "Attributes" ) );
1969 const QgsFields layerFields = vLayer->fields();
1970 for ( int idx = 0; idx < layerFields.count(); ++idx )
1971 {
1972 QgsField field = layerFields.at( idx );
1974 {
1975 continue;
1976 }
1977 // field alias in case of displayField
1978 if ( idx == displayFieldIdx )
1979 {
1980 displayField = vLayer->attributeDisplayName( idx );
1981 }
1982 QDomElement attributeElem = doc.createElement( QStringLiteral( "Attribute" ) );
1983 attributeElem.setAttribute( QStringLiteral( "name" ), field.name() );
1984 attributeElem.setAttribute( QStringLiteral( "type" ), QVariant::typeToName( field.type() ) );
1985 attributeElem.setAttribute( QStringLiteral( "typeName" ), field.typeName() );
1986 QString alias = field.alias();
1987 if ( !alias.isEmpty() )
1988 {
1989 attributeElem.setAttribute( QStringLiteral( "alias" ), alias );
1990 }
1991
1992 //edit type to text
1993 attributeElem.setAttribute( QStringLiteral( "editType" ), vLayer->editorWidgetSetup( idx ).type() );
1994 attributeElem.setAttribute( QStringLiteral( "comment" ), field.comment() );
1995 attributeElem.setAttribute( QStringLiteral( "length" ), field.length() );
1996 attributeElem.setAttribute( QStringLiteral( "precision" ), field.precision() );
1997 attributesElem.appendChild( attributeElem );
1998 }
1999
2000 //displayfield
2001 layerElem.setAttribute( QStringLiteral( "displayField" ), displayField );
2002
2003 //primary key
2004 QgsAttributeList pkAttributes = vLayer->primaryKeyAttributes();
2005 if ( pkAttributes.size() > 0 )
2006 {
2007 QDomElement pkElem = doc.createElement( QStringLiteral( "PrimaryKey" ) );
2008 QgsAttributeList::const_iterator pkIt = pkAttributes.constBegin();
2009 for ( ; pkIt != pkAttributes.constEnd(); ++pkIt )
2010 {
2011 QDomElement pkAttributeElem = doc.createElement( QStringLiteral( "PrimaryKeyAttribute" ) );
2012 QDomText pkAttName = doc.createTextNode( layerFields.at( *pkIt ).name() );
2013 pkAttributeElem.appendChild( pkAttName );
2014 pkElem.appendChild( pkAttributeElem );
2015 }
2016 layerElem.appendChild( pkElem );
2017 }
2018
2019 //geometry type
2020 layerElem.setAttribute( QStringLiteral( "geometryType" ), QgsWkbTypes::displayString( vLayer->wkbType() ) );
2021
2022 //opacity
2023 layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( vLayer->opacity() ) );
2024
2025 layerElem.appendChild( attributesElem );
2026 break;
2027 }
2028
2030 {
2031 const QgsDataProvider *provider = currentLayer->dataProvider();
2032 if ( provider && provider->name() == "wms" )
2033 {
2034 //advertise as web map background layer
2035 QVariant wmsBackgroundLayer = currentLayer->customProperty( QStringLiteral( "WMSBackgroundLayer" ), false );
2036 QDomElement wmsBackgroundLayerElem = doc.createElement( "WMSBackgroundLayer" );
2037 QDomText wmsBackgroundLayerText = doc.createTextNode( wmsBackgroundLayer.toBool() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2038 wmsBackgroundLayerElem.appendChild( wmsBackgroundLayerText );
2039 layerElem.appendChild( wmsBackgroundLayerElem );
2040
2041 //publish datasource
2042 QVariant wmsPublishDataSourceUrl = currentLayer->customProperty( QStringLiteral( "WMSPublishDataSourceUrl" ), false );
2043 if ( wmsPublishDataSourceUrl.toBool() )
2044 {
2045 bool tiled = qobject_cast< const QgsRasterDataProvider * >( provider )
2046 ? !qobject_cast< const QgsRasterDataProvider * >( provider )->nativeResolutions().isEmpty()
2047 : false;
2048
2049 QDomElement dataSourceElem = doc.createElement( tiled ? QStringLiteral( "WMTSDataSource" ) : QStringLiteral( "WMSDataSource" ) );
2050 QDomText dataSourceUri = doc.createTextNode( provider->dataSourceUri() );
2051 dataSourceElem.appendChild( dataSourceUri );
2052 layerElem.appendChild( dataSourceElem );
2053 }
2054 }
2055
2056 QVariant wmsPrintLayer = currentLayer->customProperty( QStringLiteral( "WMSPrintLayer" ) );
2057 if ( wmsPrintLayer.isValid() )
2058 {
2059 QDomElement wmsPrintLayerElem = doc.createElement( "WMSPrintLayer" );
2060 QDomText wmsPrintLayerText = doc.createTextNode( wmsPrintLayer.toString() );
2061 wmsPrintLayerElem.appendChild( wmsPrintLayerText );
2062 layerElem.appendChild( wmsPrintLayerElem );
2063 }
2064
2065 //opacity
2066 QgsRasterLayer *rl = static_cast<QgsRasterLayer *>( currentLayer );
2067 QgsRasterRenderer *rasterRenderer = rl->renderer();
2068 if ( rasterRenderer )
2069 {
2070 layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( rasterRenderer->opacity() ) );
2071 }
2072 break;
2073 }
2074
2081 break;
2082 }
2083 }
2084
2085 void addKeywordListElement( const QgsProject *project, QDomDocument &doc, QDomElement &parent )
2086 {
2087 bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
2088
2089 QDomElement keywordsElem = doc.createElement( QStringLiteral( "KeywordList" ) );
2090 //add default keyword
2091 QDomElement keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
2092 keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "ISO" ) );
2093 QDomText keywordText = doc.createTextNode( QStringLiteral( "infoMapAccessService" ) );
2094 keywordElem.appendChild( keywordText );
2095 keywordsElem.appendChild( keywordElem );
2096 parent.appendChild( keywordsElem );
2097 QStringList keywords = QgsServerProjectUtils::owsServiceKeywords( *project );
2098 for ( const QString &keyword : std::as_const( keywords ) )
2099 {
2100 if ( !keyword.isEmpty() )
2101 {
2102 keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
2103 keywordText = doc.createTextNode( keyword );
2104 keywordElem.appendChild( keywordText );
2105 if ( sia2045 )
2106 {
2107 keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "SIA_Geo405" ) );
2108 }
2109 keywordsElem.appendChild( keywordElem );
2110 }
2111 }
2112 parent.appendChild( keywordsElem );
2113 }
2114 }
2115
2116 bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers )
2117 {
2118 if ( childNode->nodeType() == QgsLayerTreeNode::NodeGroup )
2119 {
2120 for ( int j = 0; j < childNode->children().size(); ++j )
2121 {
2122 if ( hasQueryableChildren( childNode->children().at( j ), wmsRestrictedLayers ) )
2123 return true;
2124 }
2125 return false;
2126 }
2127 else if ( childNode->nodeType() == QgsLayerTreeNode::NodeLayer )
2128 {
2129 const auto treeLayer { static_cast<const QgsLayerTreeLayer *>( childNode ) };
2130 const auto l { treeLayer->layer() };
2131 if ( l )
2132 {
2133 return ! wmsRestrictedLayers.contains( l->name() ) && l->flags().testFlag( QgsMapLayer::Identifiable );
2134 }
2135 else
2136 {
2137 QgsMessageLog::logMessage( QStringLiteral( "Broken/corrupted layer tree, layer '%1' does not exist: check your project!" ).arg( treeLayer->name() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
2138 }
2139 }
2140 return false;
2141 }
2142
2143
2144} // namespace QgsWms
A helper class that centralizes restrictions given by all the access control filter plugins.
bool layerReadPermission(const QgsMapLayer *layer) const
Returns the layer read right.
bool fillCacheKey(QStringList &cacheKey) const
Fill the capabilities caching key.
A cache for capabilities xml documents (by configuration file path)
const QDomDocument * searchCapabilitiesDocument(const QString &configFilePath, const QString &key)
Returns cached capabilities document (or 0 if document for configuration file not in cache)
void insertCapabilitiesDocument(const QString &configFilePath, const QString &key, const QDomDocument *doc)
Inserts new capabilities document (creates a copy of the document, does not take ownership)
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Abstract base class for spatial data provider implementations.
virtual QString name() const =0
Returns a provider name.
virtual QString dataSourceUri(bool expandAuthConfig=false) const
Gets the data source specification.
QString what() const
Definition: qgsexception.h:48
An expression node which takes it value from a feature's field.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString typeName() const
Gets the field type.
Definition: qgsfield.cpp:140
QString name
Definition: qgsfield.h:60
int precision
Definition: qgsfield.h:57
int length
Definition: qgsfield.h:56
ConfigurationFlags configurationFlags
Definition: qgsfield.h:64
QVariant::Type type
Definition: qgsfield.h:58
QString alias
Definition: qgsfield.h:61
QString comment
Definition: qgsfield.h:59
@ HideFromWms
Field is not available if layer is served as WMS from QGIS server.
Container of fields for a vector layer.
Definition: qgsfields.h:45
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
Layer tree group node serves as a container for layers and further groups.
QString name() const override
Returns the group's name.
bool isMutuallyExclusive() const
Returns whether the group is mutually exclusive (only one child can be checked at a time)
Layer tree node points to a map layer.
QString name() const override
Returns the layer's name.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
This class is a base class for nodes in a layer tree.
@ NodeGroup
Container of other groups and layers.
@ NodeLayer
Leaf node pointing to a layer.
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well)
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
NodeType nodeType() const
Find out about type of the node. It is usually shorter to use convenience functions from QgsLayerTree...
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
bool itemVisibilityChecked() const
Returns whether a node is checked (independently of its ancestors or children)
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:33
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Class used to render QgsLayout as an atlas, by iterating over the features from an associated vector ...
bool enabled() const
Returns whether the atlas generation is enabled.
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
A layout multiframe subclass for HTML content.
A layout item subclass for text labels.
Layout graphical items for displaying a map.
QString displayName() const override
Gets item display name.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
QString id() const
Returns the item's ID name.
QList< QgsPrintLayout * > printLayouts() const
Returns a list of all print layouts contained in the manager.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
double length() const
Returns the length of the measurement.
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
int pageCount() const
Returns the number of pages in the collection.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:122
void layoutObjects(QList< T * > &objectList) const
Returns a list of layout objects (items and multiframes) of a specific type.
Definition: qgslayout.h:141
QgsLayoutMeasurement convertFromLayoutUnits(double length, QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout's native units to a specified target unit.
Definition: qgslayout.cpp:344
Manages QGIS Server properties for a map layer.
QStringList styles() const
Returns list of all defined style names.
virtual QgsDateTimeRange calculateTemporalExtent(QgsMapLayer *layer) const
Attempts to calculate the overall temporal extent for the specified layer, using the settings defined...
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:73
QString name
Definition: qgsmaplayer.h:76
QString legendUrlFormat() const
Returns the format for a URL based layer legend.
Definition: qgsmaplayer.h:1298
QgsRectangle wgs84Extent(bool forceRecalculate=false) const
Returns the WGS84 extent (EPSG:4326) of the layer according to ReadFlag::FlagTrustLayerMetadata.
virtual QgsRectangle extent() const
Returns the extent of the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Definition: qgsmaplayer.h:426
QString attribution() const
Returns the attribution of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:400
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QString abstract() const
Returns the abstract of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:326
QString dataUrlFormat() const
Returns the DataUrl format of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:380
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
QString title() const
Returns the title of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:310
QString dataUrl() const
Returns the DataUrl of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:362
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1503
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:145
QString attributionUrl() const
Returns the attribution URL of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:418
double minimumScale() const
Returns the minimum map scale (i.e.
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
QString legendUrl() const
Returns the URL for the layer's legend.
Definition: qgsmaplayer.h:1288
double opacity
Definition: qgsmaplayer.h:82
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
double maximumScale() const
Returns the maximum map scale (i.e.
QString keywordList() const
Returns the keyword list of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:342
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Print layout, a QgsLayout subclass for static or atlas-based layouts.
QgsLayoutAtlas * atlas()
Returns the print layout's atlas.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
QString title() const
Returns the project's title.
Definition: qgsproject.cpp:500
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:110
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
const QgsLayoutManager * layoutManager() const
Returns the project's layout manager, which manages print layouts, atlases and reports within the pro...
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:109
Represents a raster layer.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
Raster renderer pipe that applies colors to a raster.
double opacity() const
Returns the opacity for the renderer, where opacity is a value between 0 (totally transparent) and 1....
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
void invert()
Swap x/y coordinates in the rectangle.
Definition: qgsrectangle.h:575
A helper class that centralizes caches accesses given by all the server cache filter plugins.
bool setCachedDocument(const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Updates or inserts the document in cache like capabilities.
bool getCachedDocument(QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Returns cached document (or 0 if document not in cache) like capabilities.
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins.
virtual QgsServerCacheManager * cacheManager() const =0
Gets the registered server cache filters.
virtual QString configFilePath()=0
Returns the configuration file path.
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
virtual QgsCapabilitiesCache * capabilitiesCache()=0
Gets pointer to the capabiblities cache.
QList< QgsServerMetadataUrlProperties::MetadataUrl > metadataUrls() const
Returns a list of metadataUrl resources associated for the layer.
QString service() const
Returns SERVICE parameter as a string or an empty string if not defined.
QgsServerParameters serverParameters() const
Returns parameters.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
Provides a way to retrieve settings by prioritizing according to environment variables,...
bool getPrintDisabled() const
Returns true if WMS GetPrint request is disabled and the project's reading flag QgsProject::ReadFlag:...
const QList< QgsServerWmsDimensionProperties::WmsDimensionInfo > wmsDimensions() const
Returns the QGIS Server WMS Dimension list.
bool isActive() const
Returns true if the temporal property is active.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString displayExpression
QgsEditorWidgetSetup editorWidgetSetup(int index) const
The editor widget setup defines which QgsFieldFormatter and editor widget will be used for the field ...
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
QString version() const override
Returns VERSION parameter as a string or an empty string if not defined.
Class defining request interface passed to WMS service.
Definition: qgswmsrequest.h:35
const QgsWmsParameters & wmsParameters() const
Returns the parameters interpreted for the WMS service.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
SERVER_EXPORT QString wmsRootName(const QgsProject &project)
Returns the WMS root layer name defined in a QGIS project.
SERVER_EXPORT bool wmsInfoFormatSia2045(const QgsProject &project)
Returns if the info format is SIA20145.
SERVER_EXPORT QString wmsInspireMetadataUrl(const QgsProject &project)
Returns the Inspire metadata URL.
SERVER_EXPORT double ceilWithPrecision(double number, int places)
Returns a double greater than number to the specified number of places.
SERVER_EXPORT QStringList wmsRestrictedComposers(const QgsProject &project)
Returns the restricted composer list.
SERVER_EXPORT QgsRectangle wmsExtent(const QgsProject &project)
Returns the WMS Extent restriction.
SERVER_EXPORT bool wmsUseLayerIds(const QgsProject &project)
Returns if layer ids are used as name in WMS.
SERVER_EXPORT QString owsServiceAccessConstraints(const QgsProject &project)
Returns the owsService access constraints defined in project.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
SERVER_EXPORT QString owsServiceOnlineResource(const QgsProject &project)
Returns the owsService online resource defined in project.
SERVER_EXPORT QString owsServiceFees(const QgsProject &project)
Returns the owsService fees defined in project.
SERVER_EXPORT QStringList owsServiceKeywords(const QgsProject &project)
Returns the owsService keywords defined in project.
SERVER_EXPORT QString owsServiceContactPosition(const QgsProject &project)
Returns the owsService contact position defined in project.
SERVER_EXPORT QString serviceUrl(const QString &service, const QgsServerRequest &request, const QgsServerSettings &settings)
Returns the service url defined in the environment variable or with HTTP header.
SERVER_EXPORT QString wmsInspireTemporalReference(const QgsProject &project)
Returns the Inspire temporal reference.
SERVER_EXPORT QStringList wmsOutputCrsList(const QgsProject &project)
Returns the WMS output CRS list.
SERVER_EXPORT QString wmsInspireMetadataDate(const QgsProject &project)
Returns the Inspire metadata date.
SERVER_EXPORT QString owsServiceContactOrganization(const QgsProject &project)
Returns the owsService contact organization defined in project.
SERVER_EXPORT QStringList wmsRestrictedLayers(const QgsProject &project)
Returns the restricted layer name list.
SERVER_EXPORT QString wmsInspireLanguage(const QgsProject &project)
Returns the Inspire language.
SERVER_EXPORT QString wmsInspireMetadataUrlType(const QgsProject &project)
Returns the Inspire metadata URL type.
SERVER_EXPORT bool wmsInspireActivate(const QgsProject &project)
Returns if Inspire is activated.
SERVER_EXPORT int wmsMaxWidth(const QgsProject &project)
Returns the maximum width for WMS images defined in a QGIS project.
SERVER_EXPORT QString owsServiceTitle(const QgsProject &project)
Returns the owsService title defined in project.
SERVER_EXPORT QString owsServiceContactMail(const QgsProject &project)
Returns the owsService contact mail defined in project.
SERVER_EXPORT QString owsServiceAbstract(const QgsProject &project)
Returns the owsService abstract defined in project.
SERVER_EXPORT double floorWithPrecision(double number, int places)
Returns a double less than number to the specified number of places.
SERVER_EXPORT int wmsMaxHeight(const QgsProject &project)
Returns the maximum height for WMS images defined in a QGIS project.
SERVER_EXPORT QString owsServiceContactPhone(const QgsProject &project)
Returns the owsService contact phone defined in project.
SERVER_EXPORT QString owsServiceContactPerson(const QgsProject &project)
Returns the owsService contact person defined in project.
Median cut implementation.
QDomElement getWFSLayersElement(QDomDocument &doc, const QgsProject *project)
Create WFSLayers element for get capabilities document.
void writeGetCapabilities(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response, bool projectSettings)
Output GetCapabilities response.
QDomElement getLayersAndStylesCapabilitiesElement(QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings)
Create element for get capabilities document.
QDomElement getInspireCapabilitiesElement(QDomDocument &doc, const QgsProject *project)
Create InspireCapabilities element for get capabilities document.
QDomElement getComposerTemplatesElement(QDomDocument &doc, const QgsProject *project)
Create ComposerTemplates element for get capabilities document.
QDomElement getServiceElement(QDomDocument &doc, const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *serverSettings)
Create Service element for get capabilities document.
QDomElement getCapabilityElement(QDomDocument &doc, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings, QgsServerInterface *serverIface)
Create Capability element for get capabilities document.
bool hasQueryableChildren(const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers)
QDomDocument getCapabilities(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings)
Creates the WMS GetCapabilities XML document.
QUrl serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Returns WMS service URL.
Definition: qgswmsutils.cpp:38
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2466
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
CONSTLATIN1STRING geoEpsgCrsAuthId()
Geographic coord sys from EPSG authority.
Definition: qgis.h:2973
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463
const QgsCoordinateReferenceSystem & crs
int precision
const double OGC_PX_M
Setting to define QGIS Server WMS Dimension.
@ MaxValue
Modify current selection to include only select features which match.