QGIS API Documentation  3.25.0-Master (10b47c2603)
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"
22 #include "qgswmsgetcapabilities.h"
23 #include "qgsserverprojectutils.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"
38 #include "qgsexpressionnodeimpl.h"
39 #include "qgsvectorlayer.h"
40 #include "qgsrasterdataprovider.h"
41 #include "qgsrasterlayer.h"
42 #include "qgsrasterrenderer.h"
44 
45 
46 namespace 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();
696  if ( QgsServerProjectUtils::wmsUseLayerIds( *project ) )
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  mapId++;
721  composerMapElem.setAttribute( QStringLiteral( "width" ), composerMap->rect().width() );
722  composerMapElem.setAttribute( QStringLiteral( "height" ), composerMap->rect().height() );
723  composerTemplateElem.appendChild( composerMapElem );
724  }
725 
726  //add available composer labels
727  QList<QgsLayoutItemLabel *> composerLabelList;
728  layout->layoutItems<QgsLayoutItemLabel>( composerLabelList );
729  QList<QgsLayoutItemLabel *>::const_iterator clIt = composerLabelList.constBegin();
730  for ( ; clIt != composerLabelList.constEnd(); ++clIt )
731  {
732  QgsLayoutItemLabel *composerLabel = *clIt;
733  QString id = composerLabel->id();
734  if ( id.isEmpty() )
735  continue;
736 
737  QDomElement composerLabelElem = doc.createElement( QStringLiteral( "ComposerLabel" ) );
738  composerLabelElem.setAttribute( QStringLiteral( "name" ), id );
739  composerTemplateElem.appendChild( composerLabelElem );
740  }
741 
742  //add available composer HTML
743  QList<QgsLayoutItemHtml *> composerHtmlList;
744  layout->layoutObjects<QgsLayoutItemHtml>( composerHtmlList );
745  QList<QgsLayoutItemHtml *>::const_iterator chIt = composerHtmlList.constBegin();
746  for ( ; chIt != composerHtmlList.constEnd(); ++chIt )
747  {
748  QgsLayoutItemHtml *composerHtml = *chIt;
749  if ( composerHtml->frameCount() == 0 )
750  continue;
751 
752  QString id = composerHtml->frame( 0 )->id();
753  if ( id.isEmpty() )
754  continue;
755 
756  QDomElement composerHtmlElem = doc.createElement( QStringLiteral( "ComposerHtml" ) );
757  composerHtmlElem.setAttribute( QStringLiteral( "name" ), id );
758  composerTemplateElem.appendChild( composerHtmlElem );
759  }
760 
761  composerTemplatesElem.appendChild( composerTemplateElem );
762  }
763 
764  if ( composerTemplatesElem.childNodes().size() == 0 )
765  return QDomElement();
766 
767  return composerTemplatesElem;
768  }
769 
770  QDomElement getWFSLayersElement( QDomDocument &doc, const QgsProject *project )
771  {
772  QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
773  if ( wfsLayerIds.size() == 0 )
774  return QDomElement();
775 
776  QDomElement wfsLayersElem = doc.createElement( QStringLiteral( "WFSLayers" ) );
777  for ( int i = 0; i < wfsLayerIds.size(); ++i )
778  {
779  QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) );
780  if ( ! layer || layer->type() != QgsMapLayerType::VectorLayer )
781  {
782  continue;
783  }
784 
785  QDomElement wfsLayerElem = doc.createElement( QStringLiteral( "WFSLayer" ) );
786  if ( QgsServerProjectUtils::wmsUseLayerIds( *project ) )
787  {
788  wfsLayerElem.setAttribute( QStringLiteral( "name" ), layer->id() );
789  }
790  else
791  {
792  wfsLayerElem.setAttribute( QStringLiteral( "name" ), layer->name() );
793  }
794  wfsLayersElem.appendChild( wfsLayerElem );
795  }
796 
797  return wfsLayersElem;
798  }
799 
800  QDomElement getLayersAndStylesCapabilitiesElement( QDomDocument &doc, QgsServerInterface *serverIface,
801  const QgsProject *project,
802  const QgsWmsRequest &request, bool projectSettings )
803  {
804  const QgsLayerTree *projectLayerTreeRoot = project->layerTreeRoot();
805 
806  QDomElement layerParentElem = doc.createElement( QStringLiteral( "Layer" ) );
807 
808  // Root Layer name
809  QString rootLayerName = QgsServerProjectUtils::wmsRootName( *project );
810  if ( rootLayerName.isEmpty() && !project->title().isEmpty() )
811  {
812  rootLayerName = project->title();
813  }
814 
815  if ( !rootLayerName.isEmpty() )
816  {
817  QDomElement layerParentNameElem = doc.createElement( QStringLiteral( "Name" ) );
818  QDomText layerParentNameText = doc.createTextNode( rootLayerName );
819  layerParentNameElem.appendChild( layerParentNameText );
820  layerParentElem.appendChild( layerParentNameElem );
821  }
822 
823  // Root Layer title
824  QDomElement layerParentTitleElem = doc.createElement( QStringLiteral( "Title" ) );
825  QDomText layerParentTitleText = doc.createTextNode( QgsServerProjectUtils::owsServiceTitle( *project ) );
826  layerParentTitleElem.appendChild( layerParentTitleText );
827  layerParentElem.appendChild( layerParentTitleElem );
828 
829  // Root Layer abstract
830  const QString rootLayerAbstract = QgsServerProjectUtils::owsServiceAbstract( *project );
831  if ( !rootLayerAbstract.isEmpty() )
832  {
833  QDomElement layerParentAbstElem = doc.createElement( QStringLiteral( "Abstract" ) );
834  QDomText layerParentAbstText = doc.createCDATASection( rootLayerAbstract );
835  layerParentAbstElem.appendChild( layerParentAbstText );
836  layerParentElem.appendChild( layerParentAbstElem );
837  }
838 
839  // Keyword list
840  addKeywordListElement( project, doc, layerParentElem );
841 
842  // Root Layer tree name
843  if ( projectSettings )
844  {
845  QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
846  QDomText treeNameText = doc.createTextNode( project->title() );
847  treeNameElem.appendChild( treeNameText );
848  layerParentElem.appendChild( treeNameElem );
849  }
850 
851  if ( hasQueryableChildren( projectLayerTreeRoot, QgsServerProjectUtils::wmsRestrictedLayers( *project ) ) )
852  {
853  layerParentElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
854  }
855  else
856  {
857  layerParentElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
858  }
859 
860  appendLayersFromTreeGroup( doc, layerParentElem, serverIface, project, request, projectLayerTreeRoot, projectSettings );
861 
862  combineExtentAndCrsOfGroupChildren( doc, layerParentElem, project, true );
863 
864  return layerParentElem;
865  }
866 
867  namespace
868  {
869 
870  void appendLayersFromTreeGroup( QDomDocument &doc,
871  QDomElement &parentLayer,
872  QgsServerInterface *serverIface,
873  const QgsProject *project,
874  const QgsWmsRequest &request,
875  const QgsLayerTreeGroup *layerTreeGroup,
876  bool projectSettings )
877  {
878  const QString version = request.wmsParameters().version();
879 
880  bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
881  bool siaFormat = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
882  const QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
883 
884  QList< QgsLayerTreeNode * > layerTreeGroupChildren = layerTreeGroup->children();
885  for ( int i = 0; i < layerTreeGroupChildren.size(); ++i )
886  {
887  QgsLayerTreeNode *treeNode = layerTreeGroupChildren.at( i );
888  QDomElement layerElem = doc.createElement( QStringLiteral( "Layer" ) );
889 
890  if ( projectSettings )
891  {
892  layerElem.setAttribute( QStringLiteral( "visible" ), treeNode->isVisible() );
893  layerElem.setAttribute( QStringLiteral( "visibilityChecked" ), treeNode->itemVisibilityChecked() );
894  layerElem.setAttribute( QStringLiteral( "expanded" ), treeNode->isExpanded() );
895  }
896 
897  if ( treeNode->nodeType() == QgsLayerTreeNode::NodeGroup )
898  {
899  QgsLayerTreeGroup *treeGroupChild = static_cast<QgsLayerTreeGroup *>( treeNode );
900 
901  QString name = treeGroupChild->name();
902  if ( restrictedLayers.contains( name ) ) //unpublished group
903  {
904  continue;
905  }
906 
907  if ( projectSettings )
908  {
909  layerElem.setAttribute( QStringLiteral( "mutuallyExclusive" ), treeGroupChild->isMutuallyExclusive() );
910  }
911 
912  QString shortName = treeGroupChild->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
913  QString title = treeGroupChild->customProperty( QStringLiteral( "wmsTitle" ) ).toString();
914 
915  QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
916  QDomText nameText;
917  if ( !shortName.isEmpty() )
918  nameText = doc.createTextNode( shortName );
919  else
920  nameText = doc.createTextNode( name );
921  nameElem.appendChild( nameText );
922  layerElem.appendChild( nameElem );
923 
924  QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) );
925  QDomText titleText;
926  if ( !title.isEmpty() )
927  titleText = doc.createTextNode( title );
928  else
929  titleText = doc.createTextNode( name );
930  titleElem.appendChild( titleText );
931  layerElem.appendChild( titleElem );
932 
933  QString abstract = treeGroupChild->customProperty( QStringLiteral( "wmsAbstract" ) ).toString();
934  if ( !abstract.isEmpty() )
935  {
936  QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) );
937  QDomText abstractText = doc.createTextNode( abstract );
938  abstractElem.appendChild( abstractText );
939  layerElem.appendChild( abstractElem );
940  }
941 
942  // Layer tree name
943  if ( projectSettings )
944  {
945  QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
946  QDomText treeNameText = doc.createTextNode( name );
947  treeNameElem.appendChild( treeNameText );
948  layerElem.appendChild( treeNameElem );
949  }
950 
951  // Set queryable if any of the children are
952  if ( hasQueryableChildren( treeNode, restrictedLayers ) )
953  {
954  layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
955  }
956  else
957  {
958  layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
959  }
960 
961  appendLayersFromTreeGroup( doc, layerElem, serverIface, project, request, treeGroupChild, projectSettings );
962 
963  combineExtentAndCrsOfGroupChildren( doc, layerElem, project );
964  }
965  else
966  {
967  QgsLayerTreeLayer *treeLayer = static_cast<QgsLayerTreeLayer *>( treeNode );
968  QgsMapLayer *l = treeLayer->layer();
969  if ( !l || restrictedLayers.contains( l->name() ) ) //unpublished layer
970  {
971  continue;
972  }
973 
974 #ifdef HAVE_SERVER_PYTHON_PLUGINS
975  QgsAccessControl *accessControl = serverIface->accessControls();
976  if ( accessControl && !accessControl->layerReadPermission( l ) )
977  {
978  continue;
979  }
980 #endif
981  QString wmsName = l->name();
982  if ( useLayerIds )
983  {
984  wmsName = l->id();
985  }
986  else if ( !l->shortName().isEmpty() )
987  {
988  wmsName = l->shortName();
989  }
990 
991  // queryable layer
992  if ( !l->flags().testFlag( QgsMapLayer::Identifiable ) )
993  {
994  layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
995  }
996  else
997  {
998  layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
999  }
1000 
1001  QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
1002  QDomText nameText = doc.createTextNode( wmsName );
1003  nameElem.appendChild( nameText );
1004  layerElem.appendChild( nameElem );
1005 
1006  QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) );
1007  QString title = l->title();
1008  if ( title.isEmpty() )
1009  {
1010  title = l->name();
1011  }
1012  QDomText titleText = doc.createTextNode( title );
1013  titleElem.appendChild( titleText );
1014  layerElem.appendChild( titleElem );
1015 
1016  QString abstract = l->abstract();
1017  if ( !abstract.isEmpty() )
1018  {
1019  QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) );
1020  QDomText abstractText = doc.createTextNode( abstract );
1021  abstractElem.appendChild( abstractText );
1022  layerElem.appendChild( abstractElem );
1023  }
1024 
1025  //keyword list
1026  if ( !l->keywordList().isEmpty() )
1027  {
1028  QStringList keywordStringList = l->keywordList().split( ',' );
1029 
1030  QDomElement keywordListElem = doc.createElement( QStringLiteral( "KeywordList" ) );
1031  for ( int i = 0; i < keywordStringList.size(); ++i )
1032  {
1033  QDomElement keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
1034  QDomText keywordText = doc.createTextNode( keywordStringList.at( i ).trimmed() );
1035  keywordElem.appendChild( keywordText );
1036  if ( siaFormat )
1037  {
1038  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "SIA_Geo405" ) );
1039  }
1040  keywordListElem.appendChild( keywordElem );
1041  }
1042  layerElem.appendChild( keywordListElem );
1043  }
1044 
1045  //vector layer without geometry
1046  bool geometryLayer = true;
1047  if ( l->type() == QgsMapLayerType::VectorLayer )
1048  {
1049  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( l );
1050  if ( vLayer )
1051  {
1052  if ( vLayer->wkbType() == QgsWkbTypes::NoGeometry )
1053  {
1054  geometryLayer = false;
1055  }
1056  }
1057  }
1058 
1059  //CRS
1060  if ( geometryLayer )
1061  {
1062  QStringList crsList;
1063  crsList << l->crs().authid();
1064  QStringList outputCrsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
1065  appendCrsElementsToLayer( doc, layerElem, crsList, outputCrsList );
1066 
1067  //Ex_GeographicBoundingBox
1068  QgsRectangle extent = l->extent(); // layer extent by default
1069  QgsRectangle wgs84Extent;
1070  if ( extent.isEmpty() )
1071  {
1072  // if the extent is empty (not only Null), use the wms extent
1073  // defined in the project...
1074  extent = QgsServerProjectUtils::wmsExtent( *project );
1075  if ( extent.isNull() )
1076  {
1077  // or the CRS extent otherwise
1078  extent = l->crs().bounds();
1079  }
1080  else if ( l->crs() != project->crs() )
1081  {
1082  // If CRS is different transform it to layer's CRS
1083  try
1084  {
1085  QgsCoordinateTransform ct( project->crs(), l->crs(), project->transformContext() );
1086  extent = ct.transform( extent );
1087  }
1088  catch ( QgsCsException &cse )
1089  {
1090  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent for layer %1: %2" ).arg( l->name() ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1091  continue;
1092  }
1093  }
1094  }
1095  else
1096  {
1097  // Get the wgs84 extent from layer
1098  wgs84Extent = l->wgs84Extent();
1099  }
1100 
1101  appendLayerBoundingBoxes( doc, layerElem, extent, l->crs(), crsList, outputCrsList, project, wgs84Extent );
1102  }
1103 
1104  // add details about supported styles of the layer
1105  appendLayerStyles( doc, layerElem, l, project, request, serverIface->serverSettings() );
1106 
1107  //min/max scale denominatorScaleBasedVisibility
1108  if ( l->hasScaleBasedVisibility() )
1109  {
1110  if ( version == QLatin1String( "1.1.1" ) )
1111  {
1112  double OGC_PX_M = 0.00028; // OGC reference pixel size in meter, also used by qgis
1113  double SCALE_TO_SCALEHINT = OGC_PX_M * M_SQRT2;
1114 
1115  QDomElement scaleHintElem = doc.createElement( QStringLiteral( "ScaleHint" ) );
1116  scaleHintElem.setAttribute( QStringLiteral( "min" ), QString::number( l->maximumScale() * SCALE_TO_SCALEHINT ) );
1117  scaleHintElem.setAttribute( QStringLiteral( "max" ), QString::number( l->minimumScale() * SCALE_TO_SCALEHINT ) );
1118  layerElem.appendChild( scaleHintElem );
1119  }
1120  else
1121  {
1122  QString minScaleString = QString::number( l->maximumScale() );
1123  QDomElement minScaleElem = doc.createElement( QStringLiteral( "MinScaleDenominator" ) );
1124  QDomText minScaleText = doc.createTextNode( minScaleString );
1125  minScaleElem.appendChild( minScaleText );
1126  layerElem.appendChild( minScaleElem );
1127 
1128  QString maxScaleString = QString::number( l->minimumScale() );
1129  QDomElement maxScaleElem = doc.createElement( QStringLiteral( "MaxScaleDenominator" ) );
1130  QDomText maxScaleText = doc.createTextNode( maxScaleString );
1131  maxScaleElem.appendChild( maxScaleText );
1132  layerElem.appendChild( maxScaleElem );
1133  }
1134  }
1135 
1136  // layer data URL
1137  QString dataUrl = l->dataUrl();
1138  if ( !dataUrl.isEmpty() )
1139  {
1140  QDomElement dataUrlElem = doc.createElement( QStringLiteral( "DataURL" ) );
1141  QDomElement dataUrlFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1142  QString dataUrlFormat = l->dataUrlFormat();
1143  QDomText dataUrlFormatText = doc.createTextNode( dataUrlFormat );
1144  dataUrlFormatElem.appendChild( dataUrlFormatText );
1145  dataUrlElem.appendChild( dataUrlFormatElem );
1146  QDomElement dataORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1147  dataORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1148  dataORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1149  dataORElem.setAttribute( QStringLiteral( "xlink:href" ), dataUrl );
1150  dataUrlElem.appendChild( dataORElem );
1151  layerElem.appendChild( dataUrlElem );
1152  }
1153 
1154  // layer attribution
1155  QString attribution = l->attribution();
1156  if ( !attribution.isEmpty() )
1157  {
1158  QDomElement attribElem = doc.createElement( QStringLiteral( "Attribution" ) );
1159  QDomElement attribTitleElem = doc.createElement( QStringLiteral( "Title" ) );
1160  QDomText attribText = doc.createTextNode( attribution );
1161  attribTitleElem.appendChild( attribText );
1162  attribElem.appendChild( attribTitleElem );
1163  QString attributionUrl = l->attributionUrl();
1164  if ( !attributionUrl.isEmpty() )
1165  {
1166  QDomElement attribORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1167  attribORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1168  attribORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1169  attribORElem.setAttribute( QStringLiteral( "xlink:href" ), attributionUrl );
1170  attribElem.appendChild( attribORElem );
1171  }
1172  layerElem.appendChild( attribElem );
1173  }
1174 
1175  // layer metadata URL
1176  const QList<QgsMapLayerServerProperties::MetadataUrl> urls = l->serverProperties()->metadataUrls();
1177  for ( const QgsMapLayerServerProperties::MetadataUrl &url : urls )
1178  {
1179  QDomElement metaUrlElem = doc.createElement( QStringLiteral( "MetadataURL" ) );
1180  QString metadataUrlType = url.type;
1181  if ( version == QLatin1String( "1.1.1" ) )
1182  {
1183  metaUrlElem.setAttribute( QStringLiteral( "type" ), metadataUrlType );
1184  }
1185  else if ( metadataUrlType == QLatin1String( "FGDC" ) )
1186  {
1187  metaUrlElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "FGDC:1998" ) );
1188  }
1189  else if ( metadataUrlType == QLatin1String( "TC211" ) )
1190  {
1191  metaUrlElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "ISO19115:2003" ) );
1192  }
1193  else
1194  {
1195  metaUrlElem.setAttribute( QStringLiteral( "type" ), metadataUrlType );
1196  }
1197  QString metadataUrlFormat = url.format;
1198  if ( !metadataUrlFormat.isEmpty() )
1199  {
1200  QDomElement metaUrlFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1201  QDomText metaUrlFormatText = doc.createTextNode( metadataUrlFormat );
1202  metaUrlFormatElem.appendChild( metaUrlFormatText );
1203  metaUrlElem.appendChild( metaUrlFormatElem );
1204  }
1205  QDomElement metaUrlORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1206  metaUrlORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1207  metaUrlORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1208  metaUrlORElem.setAttribute( QStringLiteral( "xlink:href" ), url.url );
1209  metaUrlElem.appendChild( metaUrlORElem );
1210  layerElem.appendChild( metaUrlElem );
1211  }
1212 
1213  bool timeDimensionAdded { false };
1214 
1215  // Add dimensions
1216  if ( l->type() == QgsMapLayerType::VectorLayer )
1217  {
1218  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( l );
1219  QgsMapLayerServerProperties *serverProperties = static_cast<QgsMapLayerServerProperties *>( vl->serverProperties() );
1220  const QList<QgsMapLayerServerProperties::WmsDimensionInfo> wmsDims = serverProperties->wmsDimensions();
1221  for ( const QgsMapLayerServerProperties::WmsDimensionInfo &dim : wmsDims )
1222  {
1223 
1224  int fieldIndex = vl->fields().indexOf( dim.fieldName );
1225  // Check field index
1226  if ( fieldIndex == -1 )
1227  {
1228  continue;
1229  }
1230  // get unique values
1231  QSet<QVariant> uniqueValues = vl->uniqueValues( fieldIndex );
1232 
1233  // get unique values from endfield name if define
1234  if ( !dim.endFieldName.isEmpty() )
1235  {
1236  int endFieldIndex = vl->fields().indexOf( dim.endFieldName );
1237  // Check end field index
1238  if ( endFieldIndex == -1 )
1239  {
1240  continue;
1241  }
1242  uniqueValues.unite( vl->uniqueValues( endFieldIndex ) );
1243  }
1244  // sort unique values
1245  QList<QVariant> values = qgis::setToList( uniqueValues );
1246  std::sort( values.begin(), values.end() );
1247 
1248  QDomElement dimElem = doc.createElement( QStringLiteral( "Dimension" ) );
1249  dimElem.setAttribute( QStringLiteral( "name" ), dim.name );
1250 
1251  if ( dim.name.toUpper() == QStringLiteral( "TIME" ) )
1252  {
1253  timeDimensionAdded = true;
1254  }
1255 
1256  if ( !dim.units.isEmpty() )
1257  {
1258  dimElem.setAttribute( QStringLiteral( "units" ), dim.units );
1259  }
1260  if ( !dim.unitSymbol.isEmpty() )
1261  {
1262  dimElem.setAttribute( QStringLiteral( "unitSymbol" ), dim.unitSymbol );
1263  }
1264  if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MinValue )
1265  {
1266  dimElem.setAttribute( QStringLiteral( "default" ), values.first().toString() );
1267  }
1268  else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MaxValue )
1269  {
1270  dimElem.setAttribute( QStringLiteral( "default" ), values.last().toString() );
1271  }
1272  else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::ReferenceValue )
1273  {
1274  dimElem.setAttribute( QStringLiteral( "default" ), dim.referenceValue.toString() );
1275  }
1276  dimElem.setAttribute( QStringLiteral( "multipleValues" ), QStringLiteral( "1" ) );
1277  dimElem.setAttribute( QStringLiteral( "nearestValue" ), QStringLiteral( "0" ) );
1278  if ( projectSettings )
1279  {
1280  dimElem.setAttribute( QStringLiteral( "fieldName" ), dim.fieldName );
1281  dimElem.setAttribute( QStringLiteral( "endFieldName" ), dim.endFieldName );
1282  }
1283  // values list
1284  QStringList strValues;
1285  for ( const QVariant &v : values )
1286  {
1287  strValues << v.toString();
1288  }
1289  QDomText dimValuesText = doc.createTextNode( strValues.join( QLatin1String( ", " ) ) );
1290  dimElem.appendChild( dimValuesText );
1291  layerElem.appendChild( dimElem );
1292  }
1293  }
1294 
1295  // Add WMS time dimension if not already added
1296  if ( ! timeDimensionAdded
1297  && l->temporalProperties()
1298  && l->temporalProperties()->isActive() )
1299  {
1300 
1301  QDomElement dimElem = doc.createElement( QStringLiteral( "Dimension" ) );
1302  dimElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "TIME" ) );
1303  dimElem.setAttribute( QStringLiteral( "units" ), QStringLiteral( "ISO8601" ) );
1304 
1305  // TODO: set "default" (reference value)
1306 
1307  // Add all values
1308  const QList<QgsDateTimeRange> allRanges { l->temporalProperties()->allTemporalRanges( l ) };
1309 
1310  // Apparently, for vectors allTemporalRanges is always empty :/
1311  // there is no way to know the type of range or the individual instants
1312 
1313  bool isDateList { true };
1314  bool isInstantList { true };
1315 
1316  QList<QDateTime> values;
1317  for ( const auto &r : std::as_const( allRanges ) )
1318  {
1319  if ( r.isInstant() )
1320  {
1321  if ( r.begin().time() != QTime( 0, 0, 0, 0 ) )
1322  {
1323  isDateList = false;
1324  }
1325  values.append( r.begin() );
1326  }
1327  else
1328  {
1329  isInstantList = false;
1330  break;
1331  }
1332  }
1333 
1334  // Only list individual values for list of instants,
1335  // otherwise only the extent will be shown
1336  if ( isInstantList )
1337  {
1338  // values list
1339  QStringList strValues;
1340  for ( const auto &v : values )
1341  {
1342  if ( isDateList )
1343  {
1344  strValues << v.date().toString( Qt::DateFormat::ISODate );
1345  }
1346  else
1347  {
1348  strValues << v.toString( Qt::DateFormat::ISODate );
1349  }
1350  }
1351  QDomText dimValuesText = doc.createTextNode( strValues.join( QChar( ',' ) ) );
1352  dimElem.appendChild( dimValuesText );
1353  }
1354 
1355  layerElem.appendChild( dimElem );
1356 
1357  QDomElement timeExtentElem = doc.createElement( QStringLiteral( "Extent" ) );
1358  timeExtentElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "TIME" ) );
1359 
1360  const QgsDateTimeRange timeExtent { l->temporalProperties()->calculateTemporalExtent( l ) };
1361  QString extent;
1362  if ( isDateList )
1363  {
1364  extent = QStringLiteral( "%1/%2" ).arg( timeExtent.begin().date().toString( Qt::DateFormat::ISODate ), timeExtent.end().date().toString( Qt::DateFormat::ISODate ) );
1365  }
1366  else
1367  {
1368  extent = QStringLiteral( "%1/%2" ).arg( timeExtent.begin().toString( Qt::DateFormat::ISODate ), timeExtent.end().toString( Qt::DateFormat::ISODate ) );
1369  }
1370  QDomText extentValueText = doc.createTextNode( extent );
1371  timeExtentElem.appendChild( extentValueText );
1372  layerElem.appendChild( timeExtentElem );
1373 
1374  }
1375 
1376  if ( projectSettings )
1377  {
1378  appendLayerProjectSettings( doc, layerElem, l );
1379  }
1380  }
1381 
1382  parentLayer.appendChild( layerElem );
1383  }
1384  }
1385 
1386  void appendLayerStyles( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer,
1387  const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *settings )
1388  {
1389  // Get service URL
1390  QUrl href = serviceUrl( request, project, *settings );
1391 
1392  //href needs to be a prefix
1393  QString hrefString = href.toString();
1394  hrefString.append( href.hasQuery() ? "&" : "?" );
1395  for ( const QString &styleName : currentLayer->styleManager()->styles() )
1396  {
1397  QDomElement styleElem = doc.createElement( QStringLiteral( "Style" ) );
1398  QDomElement styleNameElem = doc.createElement( QStringLiteral( "Name" ) );
1399  QDomText styleNameText = doc.createTextNode( styleName );
1400  styleNameElem.appendChild( styleNameText );
1401  QDomElement styleTitleElem = doc.createElement( QStringLiteral( "Title" ) );
1402  QDomText styleTitleText = doc.createTextNode( styleName );
1403  styleTitleElem.appendChild( styleTitleText );
1404  styleElem.appendChild( styleNameElem );
1405  styleElem.appendChild( styleTitleElem );
1406 
1407  // QString LegendURL for explicit layerbased GetLegendGraphic request
1408  QDomElement getLayerLegendGraphicElem = doc.createElement( QStringLiteral( "LegendURL" ) );
1409 
1410  QString customHrefString = currentLayer->legendUrl();
1411 
1412  QStringList getLayerLegendGraphicFormats;
1413  if ( !customHrefString.isEmpty() )
1414  {
1415  getLayerLegendGraphicFormats << currentLayer->legendUrlFormat();
1416  }
1417  else
1418  {
1419  getLayerLegendGraphicFormats << QStringLiteral( "image/png" ); // << "jpeg" << "image/jpeg"
1420  }
1421 
1422  for ( int i = 0; i < getLayerLegendGraphicFormats.size(); ++i )
1423  {
1424  QDomElement getLayerLegendGraphicFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1425  QString getLayerLegendGraphicFormat = getLayerLegendGraphicFormats[i];
1426  QDomText getLayerLegendGraphicFormatText = doc.createTextNode( getLayerLegendGraphicFormat );
1427  getLayerLegendGraphicFormatElem.appendChild( getLayerLegendGraphicFormatText );
1428  getLayerLegendGraphicElem.appendChild( getLayerLegendGraphicFormatElem );
1429  }
1430 
1431  // no parameters on custom hrefUrl, because should link directly to graphic
1432  if ( customHrefString.isEmpty() )
1433  {
1434  QString layerName = currentLayer->name();
1435  if ( QgsServerProjectUtils::wmsUseLayerIds( *project ) )
1436  layerName = currentLayer->id();
1437  else if ( !currentLayer->shortName().isEmpty() )
1438  layerName = currentLayer->shortName();
1439  QUrl mapUrl( hrefString );
1440  QUrlQuery mapUrlQuery( mapUrl.query() );
1441  mapUrlQuery.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WMS" ) );
1442  mapUrlQuery.addQueryItem( QStringLiteral( "VERSION" ), request.wmsParameters().version() );
1443  mapUrlQuery.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetLegendGraphic" ) );
1444  mapUrlQuery.addQueryItem( QStringLiteral( "LAYER" ), layerName );
1445  mapUrlQuery.addQueryItem( QStringLiteral( "FORMAT" ), QStringLiteral( "image/png" ) );
1446  mapUrlQuery.addQueryItem( QStringLiteral( "STYLE" ), styleNameText.data() );
1447  if ( request.wmsParameters().version() == QLatin1String( "1.3.0" ) )
1448  {
1449  mapUrlQuery.addQueryItem( QStringLiteral( "SLD_VERSION" ), QStringLiteral( "1.1.0" ) );
1450  }
1451  mapUrl.setQuery( mapUrlQuery );
1452  customHrefString = mapUrl.toString();
1453  }
1454 
1455  QDomElement getLayerLegendGraphicORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1456  getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1457  getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1458  getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xlink:href" ), customHrefString );
1459  getLayerLegendGraphicElem.appendChild( getLayerLegendGraphicORElem );
1460  styleElem.appendChild( getLayerLegendGraphicElem );
1461 
1462  layerElem.appendChild( styleElem );
1463  }
1464  }
1465 
1466  void appendCrsElementsToLayer( QDomDocument &doc, QDomElement &layerElement,
1467  const QStringList &crsList, const QStringList &constrainedCrsList )
1468  {
1469  if ( layerElement.isNull() )
1470  {
1471  return;
1472  }
1473 
1474  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1475 
1476  //insert the CRS elements after the title element to be in accordance with the WMS 1.3 specification
1477  QDomElement titleElement = layerElement.firstChildElement( QStringLiteral( "Title" ) );
1478  QDomElement abstractElement = layerElement.firstChildElement( QStringLiteral( "Abstract" ) );
1479  QDomElement keywordListElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1480  QDomElement CRSPrecedingElement = !keywordListElement.isNull() ? keywordListElement : !abstractElement.isNull() ? abstractElement : titleElement;
1481 
1482  if ( CRSPrecedingElement.isNull() )
1483  {
1484  // keyword list element is never empty
1485  const QDomElement keyElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1486  CRSPrecedingElement = keyElement;
1487  }
1488 
1489  //In case the number of advertised CRS is constrained
1490  if ( !constrainedCrsList.isEmpty() )
1491  {
1492  for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1493  {
1494  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, constrainedCrsList.at( i ) );
1495  }
1496  }
1497  else //no crs constraint
1498  {
1499  for ( const QString &crs : crsList )
1500  {
1501  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, crs );
1502  }
1503  }
1504 
1505  // Support for CRS:84 is mandatory (equals EPSG:4326 with reversed axis)
1506  // https://github.com/opengeospatial/ets-wms13/blob/47155399c09b200cb21382874fdb21d5fae4ab6e/src/site/markdown/index.md
1507  if ( version == QLatin1String( "1.3.0" ) )
1508  {
1509  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, QString( "CRS:84" ) );
1510  }
1511  }
1512 
1513  void appendCrsElementToLayer( QDomDocument &doc, QDomElement &layerElement, const QDomElement &precedingElement,
1514  const QString &crsText )
1515  {
1516  if ( crsText.isEmpty() )
1517  return;
1518  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1519  QDomElement crsElement = doc.createElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1520  QDomText crsTextNode = doc.createTextNode( crsText );
1521  crsElement.appendChild( crsTextNode );
1522  layerElement.insertAfter( crsElement, precedingElement );
1523  }
1524 
1525  void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent,
1526  const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList,
1527  const QStringList &constrainedCrsList, const QgsProject *project,
1528  const QgsRectangle &lGeoExtent )
1529  {
1530  if ( layerElem.isNull() )
1531  {
1532  return;
1533  }
1534 
1535  QgsRectangle layerExtent = lExtent;
1536  if ( qgsDoubleNear( layerExtent.xMinimum(), layerExtent.xMaximum() ) || qgsDoubleNear( layerExtent.yMinimum(), layerExtent.yMaximum() ) )
1537  {
1538  //layer bbox cannot be empty
1539  layerExtent.grow( 0.000001 );
1540  }
1541 
1542  QgsRectangle wgs84BoundingRect = lGeoExtent;
1543  if ( wgs84BoundingRect.isNull() )
1544  {
1546 
1547  //transform the layers native CRS into WGS84
1548  if ( !layerExtent.isNull() )
1549  {
1550  QgsCoordinateTransform exGeoTransform( layerCRS, wgs84, project );
1551  try
1552  {
1553  wgs84BoundingRect = exGeoTransform.transformBoundingBox( layerExtent );
1554  }
1555  catch ( const QgsCsException &cse )
1556  {
1557  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1558  wgs84BoundingRect = QgsRectangle();
1559  }
1560  }
1561  }
1562 
1563  if ( qgsDoubleNear( wgs84BoundingRect.xMinimum(), wgs84BoundingRect.xMaximum() ) || qgsDoubleNear( wgs84BoundingRect.yMinimum(), wgs84BoundingRect.yMaximum() ) )
1564  {
1565  //layer bbox cannot be empty
1566  wgs84BoundingRect.grow( 0.000001 );
1567  }
1568 
1569  //Ex_GeographicBoundingBox
1570  QDomElement ExGeoBBoxElement;
1571  const int wgs84precision = 6;
1572  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1573  if ( version == QLatin1String( "1.1.1" ) ) // WMS Version 1.1.1
1574  {
1575  ExGeoBBoxElement = doc.createElement( QStringLiteral( "LatLonBoundingBox" ) );
1576  ExGeoBBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1577  ExGeoBBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1578  ExGeoBBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1579  ExGeoBBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1580  }
1581  else // WMS Version 1.3.0
1582  {
1583  ExGeoBBoxElement = doc.createElement( QStringLiteral( "EX_GeographicBoundingBox" ) );
1584  QDomElement wBoundLongitudeElement = doc.createElement( QStringLiteral( "westBoundLongitude" ) );
1585  QDomText wBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1586  wBoundLongitudeElement.appendChild( wBoundLongitudeText );
1587  ExGeoBBoxElement.appendChild( wBoundLongitudeElement );
1588  QDomElement eBoundLongitudeElement = doc.createElement( QStringLiteral( "eastBoundLongitude" ) );
1589  QDomText eBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1590  eBoundLongitudeElement.appendChild( eBoundLongitudeText );
1591  ExGeoBBoxElement.appendChild( eBoundLongitudeElement );
1592  QDomElement sBoundLatitudeElement = doc.createElement( QStringLiteral( "southBoundLatitude" ) );
1593  QDomText sBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1594  sBoundLatitudeElement.appendChild( sBoundLatitudeText );
1595  ExGeoBBoxElement.appendChild( sBoundLatitudeElement );
1596  QDomElement nBoundLatitudeElement = doc.createElement( QStringLiteral( "northBoundLatitude" ) );
1597  QDomText nBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1598  nBoundLatitudeElement.appendChild( nBoundLatitudeText );
1599  ExGeoBBoxElement.appendChild( nBoundLatitudeElement );
1600  }
1601 
1602  if ( !wgs84BoundingRect.isNull() ) //LatLonBoundingBox / Ex_GeographicBounding box is optional
1603  {
1604  QDomElement lastCRSElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1605  if ( !lastCRSElem.isNull() )
1606  {
1607  layerElem.insertAfter( ExGeoBBoxElement, lastCRSElem );
1608  }
1609  else
1610  {
1611  layerElem.appendChild( ExGeoBBoxElement );
1612  }
1613  }
1614 
1615  //In case the number of advertised CRS is constrained
1616  if ( !constrainedCrsList.isEmpty() )
1617  {
1618  for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1619  {
1620  appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, constrainedCrsList.at( i ), project );
1621  }
1622  }
1623  else //no crs constraint
1624  {
1625  for ( const QString &crs : crsList )
1626  {
1627  appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, crs, project );
1628  }
1629  }
1630  }
1631 
1632 
1633  void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent,
1634  const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText,
1635  const QgsProject *project )
1636  {
1637  if ( layerElem.isNull() )
1638  {
1639  return;
1640  }
1641 
1642  if ( crsText.isEmpty() )
1643  {
1644  return;
1645  }
1646 
1647  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1648 
1650 
1651  //transform the layers native CRS into CRS
1652  QgsRectangle crsExtent;
1653  if ( !layerExtent.isNull() )
1654  {
1655  QgsCoordinateTransform crsTransform( layerCRS, crs, project );
1656  try
1657  {
1658  crsExtent = crsTransform.transformBoundingBox( layerExtent );
1659  }
1660  catch ( QgsCsException &cse )
1661  {
1662  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1663  return;
1664  }
1665  }
1666 
1667  if ( crsExtent.isNull() )
1668  {
1669  return;
1670  }
1671 
1672  int precision = 3;
1673  if ( crs.isGeographic() )
1674  {
1675  precision = 6;
1676  }
1677 
1678  //BoundingBox element
1679  QDomElement bBoxElement = doc.createElement( QStringLiteral( "BoundingBox" ) );
1680  if ( crs.isValid() )
1681  {
1682  bBoxElement.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", crs.authid() );
1683  }
1684 
1685  if ( version != QLatin1String( "1.1.1" ) && crs.hasAxisInverted() )
1686  {
1687  crsExtent.invert();
1688  }
1689 
1690  bBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.xMinimum(), precision ), precision ) );
1691  bBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.yMinimum(), precision ), precision ) );
1692  bBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.xMaximum(), precision ), precision ) );
1693  bBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.yMaximum(), precision ), precision ) );
1694 
1695  QDomElement lastBBoxElem = layerElem.lastChildElement( QStringLiteral( "BoundingBox" ) );
1696  if ( !lastBBoxElem.isNull() )
1697  {
1698  layerElem.insertAfter( bBoxElement, lastBBoxElem );
1699  }
1700  else
1701  {
1702  lastBBoxElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "LatLonBoundingBox" : "EX_GeographicBoundingBox" );
1703  if ( !lastBBoxElem.isNull() )
1704  {
1705  layerElem.insertAfter( bBoxElement, lastBBoxElem );
1706  }
1707  else
1708  {
1709  layerElem.appendChild( bBoxElement );
1710  }
1711  }
1712  }
1713 
1714  QgsRectangle layerBoundingBoxInProjectCrs( const QDomDocument &doc, const QDomElement &layerElem,
1715  const QgsProject *project )
1716  {
1717  QgsRectangle BBox;
1718  if ( layerElem.isNull() )
1719  {
1720  return BBox;
1721  }
1722 
1723  //read box coordinates and layer auth. id
1724  QDomElement boundingBoxElem = layerElem.firstChildElement( QStringLiteral( "BoundingBox" ) );
1725  if ( boundingBoxElem.isNull() )
1726  {
1727  return BBox;
1728  }
1729 
1730  double minx, miny, maxx, maxy;
1731  bool conversionOk;
1732  minx = boundingBoxElem.attribute( QStringLiteral( "minx" ) ).toDouble( &conversionOk );
1733  if ( !conversionOk )
1734  {
1735  return BBox;
1736  }
1737  miny = boundingBoxElem.attribute( QStringLiteral( "miny" ) ).toDouble( &conversionOk );
1738  if ( !conversionOk )
1739  {
1740  return BBox;
1741  }
1742  maxx = boundingBoxElem.attribute( QStringLiteral( "maxx" ) ).toDouble( &conversionOk );
1743  if ( !conversionOk )
1744  {
1745  return BBox;
1746  }
1747  maxy = boundingBoxElem.attribute( QStringLiteral( "maxy" ) ).toDouble( &conversionOk );
1748  if ( !conversionOk )
1749  {
1750  return BBox;
1751  }
1752 
1753 
1754  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1755 
1756  //create layer crs
1757  QgsCoordinateReferenceSystem layerCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( boundingBoxElem.attribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" ) );
1758  if ( !layerCrs.isValid() )
1759  {
1760  return BBox;
1761  }
1762 
1763  BBox.setXMinimum( minx );
1764  BBox.setXMaximum( maxx );
1765  BBox.setYMinimum( miny );
1766  BBox.setYMaximum( maxy );
1767 
1768  if ( version != QLatin1String( "1.1.1" ) && layerCrs.hasAxisInverted() )
1769  {
1770  BBox.invert();
1771  }
1772 
1773  //get project crs
1774  QgsCoordinateTransform t( layerCrs, project->crs(), project );
1775 
1776  //transform
1777  try
1778  {
1779  BBox = t.transformBoundingBox( BBox );
1780  }
1781  catch ( const QgsCsException &cse )
1782  {
1783  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1784  BBox = QgsRectangle();
1785  }
1786 
1787  return BBox;
1788  }
1789 
1790  bool crsSetFromLayerElement( const QDomElement &layerElement, QSet<QString> &crsSet )
1791  {
1792  if ( layerElement.isNull() )
1793  {
1794  return false;
1795  }
1796 
1797  crsSet.clear();
1798 
1799  QDomNodeList crsNodeList;
1800  crsNodeList = layerElement.elementsByTagName( QStringLiteral( "CRS" ) ); // WMS 1.3.0
1801  for ( int i = 0; i < crsNodeList.size(); ++i )
1802  {
1803  crsSet.insert( crsNodeList.at( i ).toElement().text() );
1804  }
1805 
1806  crsNodeList = layerElement.elementsByTagName( QStringLiteral( "SRS" ) ); // WMS 1.1.1
1807  for ( int i = 0; i < crsNodeList.size(); ++i )
1808  {
1809  crsSet.insert( crsNodeList.at( i ).toElement().text() );
1810  }
1811 
1812  return true;
1813  }
1814 
1815  void combineExtentAndCrsOfGroupChildren( QDomDocument &doc, QDomElement &groupElem, const QgsProject *project,
1816  bool considerMapExtent )
1817  {
1818  QgsRectangle combinedBBox;
1819  QSet<QString> combinedCRSSet;
1820  bool firstBBox = true;
1821  bool firstCRSSet = true;
1822 
1823  QDomNodeList layerChildren = groupElem.childNodes();
1824  for ( int j = 0; j < layerChildren.size(); ++j )
1825  {
1826  QDomElement childElem = layerChildren.at( j ).toElement();
1827 
1828  if ( childElem.tagName() != QLatin1String( "Layer" ) )
1829  continue;
1830 
1831  QgsRectangle bbox = layerBoundingBoxInProjectCrs( doc, childElem, project );
1832  if ( bbox.isNull() )
1833  {
1834  continue;
1835  }
1836 
1837  if ( !bbox.isEmpty() )
1838  {
1839  if ( firstBBox )
1840  {
1841  combinedBBox = bbox;
1842  firstBBox = false;
1843  }
1844  else
1845  {
1846  combinedBBox.combineExtentWith( bbox );
1847  }
1848  }
1849 
1850  //combine crs set
1851  QSet<QString> crsSet;
1852  if ( crsSetFromLayerElement( childElem, crsSet ) )
1853  {
1854  if ( firstCRSSet )
1855  {
1856  combinedCRSSet = crsSet;
1857  firstCRSSet = false;
1858  }
1859  else
1860  {
1861  combinedCRSSet.intersect( crsSet );
1862  }
1863  }
1864  }
1865 
1866  QStringList outputCrsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
1867  appendCrsElementsToLayer( doc, groupElem, qgis::setToList( combinedCRSSet ), outputCrsList );
1868 
1869  QgsCoordinateReferenceSystem groupCRS = project->crs();
1870  if ( considerMapExtent )
1871  {
1872  QgsRectangle mapRect = QgsServerProjectUtils::wmsExtent( *project );
1873  if ( !mapRect.isEmpty() )
1874  {
1875  combinedBBox = mapRect;
1876  }
1877  }
1878  appendLayerBoundingBoxes( doc, groupElem, combinedBBox, groupCRS, qgis::setToList( combinedCRSSet ), outputCrsList, project );
1879 
1880  }
1881 
1882  void appendDrawingOrder( QDomDocument &doc, QDomElement &parentElem, QgsServerInterface *serverIface,
1883  const QgsProject *project )
1884  {
1885 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1886  QgsAccessControl *accessControl = serverIface->accessControls();
1887 #else
1888  ( void )serverIface;
1889 #endif
1890  bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
1891  QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
1892 
1893  QStringList layerList;
1894 
1895  const QgsLayerTree *projectLayerTreeRoot = project->layerTreeRoot();
1896  QList< QgsMapLayer * > projectLayerOrder = projectLayerTreeRoot->layerOrder();
1897  for ( int i = 0; i < projectLayerOrder.size(); ++i )
1898  {
1899  QgsMapLayer *l = projectLayerOrder.at( i );
1900 
1901  if ( restrictedLayers.contains( l->name() ) ) //unpublished layer
1902  {
1903  continue;
1904  }
1905 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1906  if ( accessControl && !accessControl->layerReadPermission( l ) )
1907  {
1908  continue;
1909  }
1910 #endif
1911  QString wmsName = l->name();
1912  if ( useLayerIds )
1913  {
1914  wmsName = l->id();
1915  }
1916  else if ( !l->shortName().isEmpty() )
1917  {
1918  wmsName = l->shortName();
1919  }
1920 
1921  layerList << wmsName;
1922  }
1923 
1924  if ( !layerList.isEmpty() )
1925  {
1926  QStringList reversedList;
1927  reversedList.reserve( layerList.size() );
1928  for ( int i = layerList.size() - 1; i >= 0; --i )
1929  reversedList << layerList[ i ];
1930 
1931  QDomElement layerDrawingOrderElem = doc.createElement( QStringLiteral( "LayerDrawingOrder" ) );
1932  QDomText drawingOrderText = doc.createTextNode( reversedList.join( ',' ) );
1933  layerDrawingOrderElem.appendChild( drawingOrderText );
1934  parentElem.appendChild( layerDrawingOrderElem );
1935  }
1936  }
1937 
1938  void appendLayerProjectSettings( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer )
1939  {
1940  if ( !currentLayer )
1941  {
1942  return;
1943  }
1944 
1945  // Layer tree name
1946  QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
1947  QDomText treeNameText = doc.createTextNode( currentLayer->name() );
1948  treeNameElem.appendChild( treeNameText );
1949  layerElem.appendChild( treeNameElem );
1950 
1951  switch ( currentLayer->type() )
1952  {
1954  {
1955  QgsVectorLayer *vLayer = static_cast<QgsVectorLayer *>( currentLayer );
1956 
1957  int displayFieldIdx = -1;
1958  QString displayField = QStringLiteral( "maptip" );
1959  QgsExpression exp( vLayer->displayExpression() );
1960  if ( exp.isField() )
1961  {
1962  displayField = static_cast<const QgsExpressionNodeColumnRef *>( exp.rootNode() )->name();
1963  displayFieldIdx = vLayer->fields().lookupField( displayField );
1964  }
1965 
1966  //attributes
1967  QDomElement attributesElem = doc.createElement( QStringLiteral( "Attributes" ) );
1968  const QgsFields layerFields = vLayer->fields();
1969  for ( int idx = 0; idx < layerFields.count(); ++idx )
1970  {
1971  QgsField field = layerFields.at( idx );
1973  {
1974  continue;
1975  }
1976  // field alias in case of displayField
1977  if ( idx == displayFieldIdx )
1978  {
1979  displayField = vLayer->attributeDisplayName( idx );
1980  }
1981  QDomElement attributeElem = doc.createElement( QStringLiteral( "Attribute" ) );
1982  attributeElem.setAttribute( QStringLiteral( "name" ), field.name() );
1983  attributeElem.setAttribute( QStringLiteral( "type" ), QVariant::typeToName( field.type() ) );
1984  attributeElem.setAttribute( QStringLiteral( "typeName" ), field.typeName() );
1985  QString alias = field.alias();
1986  if ( !alias.isEmpty() )
1987  {
1988  attributeElem.setAttribute( QStringLiteral( "alias" ), alias );
1989  }
1990 
1991  //edit type to text
1992  attributeElem.setAttribute( QStringLiteral( "editType" ), vLayer->editorWidgetSetup( idx ).type() );
1993  attributeElem.setAttribute( QStringLiteral( "comment" ), field.comment() );
1994  attributeElem.setAttribute( QStringLiteral( "length" ), field.length() );
1995  attributeElem.setAttribute( QStringLiteral( "precision" ), field.precision() );
1996  attributesElem.appendChild( attributeElem );
1997  }
1998 
1999  //displayfield
2000  layerElem.setAttribute( QStringLiteral( "displayField" ), displayField );
2001 
2002  //primary key
2003  QgsAttributeList pkAttributes = vLayer->primaryKeyAttributes();
2004  if ( pkAttributes.size() > 0 )
2005  {
2006  QDomElement pkElem = doc.createElement( QStringLiteral( "PrimaryKey" ) );
2007  QgsAttributeList::const_iterator pkIt = pkAttributes.constBegin();
2008  for ( ; pkIt != pkAttributes.constEnd(); ++pkIt )
2009  {
2010  QDomElement pkAttributeElem = doc.createElement( QStringLiteral( "PrimaryKeyAttribute" ) );
2011  QDomText pkAttName = doc.createTextNode( layerFields.at( *pkIt ).name() );
2012  pkAttributeElem.appendChild( pkAttName );
2013  pkElem.appendChild( pkAttributeElem );
2014  }
2015  layerElem.appendChild( pkElem );
2016  }
2017 
2018  //geometry type
2019  layerElem.setAttribute( QStringLiteral( "geometryType" ), QgsWkbTypes::displayString( vLayer->wkbType() ) );
2020 
2021  //opacity
2022  layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( vLayer->opacity() ) );
2023 
2024  layerElem.appendChild( attributesElem );
2025  break;
2026  }
2027 
2029  {
2030  const QgsDataProvider *provider = currentLayer->dataProvider();
2031  if ( provider && provider->name() == "wms" )
2032  {
2033  //advertise as web map background layer
2034  QVariant wmsBackgroundLayer = currentLayer->customProperty( QStringLiteral( "WMSBackgroundLayer" ), false );
2035  QDomElement wmsBackgroundLayerElem = doc.createElement( "WMSBackgroundLayer" );
2036  QDomText wmsBackgroundLayerText = doc.createTextNode( wmsBackgroundLayer.toBool() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
2037  wmsBackgroundLayerElem.appendChild( wmsBackgroundLayerText );
2038  layerElem.appendChild( wmsBackgroundLayerElem );
2039 
2040  //publish datasource
2041  QVariant wmsPublishDataSourceUrl = currentLayer->customProperty( QStringLiteral( "WMSPublishDataSourceUrl" ), false );
2042  if ( wmsPublishDataSourceUrl.toBool() )
2043  {
2044  bool tiled = qobject_cast< const QgsRasterDataProvider * >( provider )
2045  ? !qobject_cast< const QgsRasterDataProvider * >( provider )->nativeResolutions().isEmpty()
2046  : false;
2047 
2048  QDomElement dataSourceElem = doc.createElement( tiled ? QStringLiteral( "WMTSDataSource" ) : QStringLiteral( "WMSDataSource" ) );
2049  QDomText dataSourceUri = doc.createTextNode( provider->dataSourceUri() );
2050  dataSourceElem.appendChild( dataSourceUri );
2051  layerElem.appendChild( dataSourceElem );
2052  }
2053  }
2054 
2055  QVariant wmsPrintLayer = currentLayer->customProperty( QStringLiteral( "WMSPrintLayer" ) );
2056  if ( wmsPrintLayer.isValid() )
2057  {
2058  QDomElement wmsPrintLayerElem = doc.createElement( "WMSPrintLayer" );
2059  QDomText wmsPrintLayerText = doc.createTextNode( wmsPrintLayer.toString() );
2060  wmsPrintLayerElem.appendChild( wmsPrintLayerText );
2061  layerElem.appendChild( wmsPrintLayerElem );
2062  }
2063 
2064  //opacity
2065  QgsRasterLayer *rl = static_cast<QgsRasterLayer *>( currentLayer );
2066  QgsRasterRenderer *rasterRenderer = rl->renderer();
2067  if ( rasterRenderer )
2068  {
2069  layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( rasterRenderer->opacity() ) );
2070  }
2071  break;
2072  }
2073 
2080  break;
2081  }
2082  }
2083 
2084  void addKeywordListElement( const QgsProject *project, QDomDocument &doc, QDomElement &parent )
2085  {
2086  bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
2087 
2088  QDomElement keywordsElem = doc.createElement( QStringLiteral( "KeywordList" ) );
2089  //add default keyword
2090  QDomElement keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
2091  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "ISO" ) );
2092  QDomText keywordText = doc.createTextNode( QStringLiteral( "infoMapAccessService" ) );
2093  keywordElem.appendChild( keywordText );
2094  keywordsElem.appendChild( keywordElem );
2095  parent.appendChild( keywordsElem );
2096  QStringList keywords = QgsServerProjectUtils::owsServiceKeywords( *project );
2097  for ( const QString &keyword : std::as_const( keywords ) )
2098  {
2099  if ( !keyword.isEmpty() )
2100  {
2101  keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
2102  keywordText = doc.createTextNode( keyword );
2103  keywordElem.appendChild( keywordText );
2104  if ( sia2045 )
2105  {
2106  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "SIA_Geo405" ) );
2107  }
2108  keywordsElem.appendChild( keywordElem );
2109  }
2110  }
2111  parent.appendChild( keywordsElem );
2112  }
2113  }
2114 
2115  bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers )
2116  {
2117  if ( childNode->nodeType() == QgsLayerTreeNode::NodeGroup )
2118  {
2119  for ( int j = 0; j < childNode->children().size(); ++j )
2120  {
2121  if ( hasQueryableChildren( childNode->children().at( j ), wmsRestrictedLayers ) )
2122  return true;
2123  }
2124  return false;
2125  }
2126  else if ( childNode->nodeType() == QgsLayerTreeNode::NodeLayer )
2127  {
2128  const auto treeLayer { static_cast<const QgsLayerTreeLayer *>( childNode ) };
2129  const auto l { treeLayer->layer() };
2130  if ( l )
2131  {
2132  return ! wmsRestrictedLayers.contains( l->name() ) && l->flags().testFlag( QgsMapLayer::Identifiable );
2133  }
2134  else
2135  {
2136  QgsMessageLog::logMessage( QStringLiteral( "Broken/corrupted layer tree, layer '%1' does not exist: check your project!" ).arg( treeLayer->name() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
2137  }
2138  }
2139  return false;
2140  }
2141 
2142 
2143 } // 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:139
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)
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...
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
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 ...
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
bool enabled() const
Returns whether the atlas generation is enabled.
A layout multiframe subclass for HTML content.
A layout item subclass for text labels.
Layout graphical items for displaying a map.
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:1284
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
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
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Definition: qgsmaplayer.h:426
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.
@ 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:1274
double opacity
Definition: qgsmaplayer.h:82
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1489
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:502
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 QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
virtual QgsServerCacheManager * cacheManager() const =0
Gets the registered server cache filters.
virtual QString configFilePath()=0
Returns the configuration file path.
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:33
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2014
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2062
CONSTLATIN1STRING geoEpsgCrsAuthId()
Geographic coord sys from EPSG authority.
Definition: qgis.h:2529
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.