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