QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  const QList<QgsMapLayerServerProperties::MetadataUrl> urls = l->serverProperties()->metadataUrls();
1176  for ( const QgsMapLayerServerProperties::MetadataUrl &url : urls )
1177  {
1178  QDomElement metaUrlElem = doc.createElement( QStringLiteral( "MetadataURL" ) );
1179  QString metadataUrlType = url.type;
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 = url.format;
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" ), url.url );
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  QgsMapLayerServerProperties *serverProperties = static_cast<QgsMapLayerServerProperties *>( vl->serverProperties() );
1217  const QList<QgsMapLayerServerProperties::WmsDimensionInfo> wmsDims = serverProperties->wmsDimensions();
1218  for ( const QgsMapLayerServerProperties::WmsDimensionInfo &dim : wmsDims )
1219  {
1220  int fieldIndex = vl->fields().indexOf( dim.fieldName );
1221  // Check field index
1222  if ( fieldIndex == -1 )
1223  {
1224  continue;
1225  }
1226  // get unique values
1227  QSet<QVariant> uniqueValues = vl->uniqueValues( fieldIndex );
1228 
1229  // get unique values from endfield name if define
1230  if ( !dim.endFieldName.isEmpty() )
1231  {
1232  int endFieldIndex = vl->fields().indexOf( dim.endFieldName );
1233  // Check end field index
1234  if ( endFieldIndex == -1 )
1235  {
1236  continue;
1237  }
1238  uniqueValues.unite( vl->uniqueValues( endFieldIndex ) );
1239  }
1240  // sort unique values
1241  QList<QVariant> values = qgis::setToList( uniqueValues );
1242  std::sort( values.begin(), values.end() );
1243 
1244  QDomElement dimElem = doc.createElement( QStringLiteral( "Dimension" ) );
1245  dimElem.setAttribute( QStringLiteral( "name" ), dim.name );
1246  if ( !dim.units.isEmpty() )
1247  {
1248  dimElem.setAttribute( QStringLiteral( "units" ), dim.units );
1249  }
1250  if ( !dim.unitSymbol.isEmpty() )
1251  {
1252  dimElem.setAttribute( QStringLiteral( "unitSymbol" ), dim.unitSymbol );
1253  }
1254  if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MinValue )
1255  {
1256  dimElem.setAttribute( QStringLiteral( "default" ), values.first().toString() );
1257  }
1258  else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MaxValue )
1259  {
1260  dimElem.setAttribute( QStringLiteral( "default" ), values.last().toString() );
1261  }
1262  else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::ReferenceValue )
1263  {
1264  dimElem.setAttribute( QStringLiteral( "default" ), dim.referenceValue.toString() );
1265  }
1266  dimElem.setAttribute( QStringLiteral( "multipleValues" ), QStringLiteral( "1" ) );
1267  dimElem.setAttribute( QStringLiteral( "nearestValue" ), QStringLiteral( "0" ) );
1268  // values list
1269  QStringList strValues;
1270  for ( const QVariant &v : values )
1271  {
1272  strValues << v.toString();
1273  }
1274  QDomText dimValuesText = doc.createTextNode( strValues.join( QLatin1String( ", " ) ) );
1275  dimElem.appendChild( dimValuesText );
1276  layerElem.appendChild( dimElem );
1277  }
1278  }
1279 
1280  if ( projectSettings )
1281  {
1282  appendLayerProjectSettings( doc, layerElem, l );
1283  }
1284  }
1285 
1286  parentLayer.appendChild( layerElem );
1287  }
1288  }
1289 
1290  void appendLayerStyles( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer,
1291  const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *settings )
1292  {
1293  // Get service URL
1294  QUrl href = serviceUrl( request, project, *settings );
1295 
1296  //href needs to be a prefix
1297  QString hrefString = href.toString();
1298  hrefString.append( href.hasQuery() ? "&" : "?" );
1299  for ( const QString &styleName : currentLayer->styleManager()->styles() )
1300  {
1301  QDomElement styleElem = doc.createElement( QStringLiteral( "Style" ) );
1302  QDomElement styleNameElem = doc.createElement( QStringLiteral( "Name" ) );
1303  QDomText styleNameText = doc.createTextNode( styleName );
1304  styleNameElem.appendChild( styleNameText );
1305  QDomElement styleTitleElem = doc.createElement( QStringLiteral( "Title" ) );
1306  QDomText styleTitleText = doc.createTextNode( styleName );
1307  styleTitleElem.appendChild( styleTitleText );
1308  styleElem.appendChild( styleNameElem );
1309  styleElem.appendChild( styleTitleElem );
1310 
1311  // QString LegendURL for explicit layerbased GetLegendGraphic request
1312  QDomElement getLayerLegendGraphicElem = doc.createElement( QStringLiteral( "LegendURL" ) );
1313 
1314  QString customHrefString = currentLayer->legendUrl();
1315 
1316  QStringList getLayerLegendGraphicFormats;
1317  if ( !customHrefString.isEmpty() )
1318  {
1319  getLayerLegendGraphicFormats << currentLayer->legendUrlFormat();
1320  }
1321  else
1322  {
1323  getLayerLegendGraphicFormats << QStringLiteral( "image/png" ); // << "jpeg" << "image/jpeg"
1324  }
1325 
1326  for ( int i = 0; i < getLayerLegendGraphicFormats.size(); ++i )
1327  {
1328  QDomElement getLayerLegendGraphicFormatElem = doc.createElement( QStringLiteral( "Format" ) );
1329  QString getLayerLegendGraphicFormat = getLayerLegendGraphicFormats[i];
1330  QDomText getLayerLegendGraphicFormatText = doc.createTextNode( getLayerLegendGraphicFormat );
1331  getLayerLegendGraphicFormatElem.appendChild( getLayerLegendGraphicFormatText );
1332  getLayerLegendGraphicElem.appendChild( getLayerLegendGraphicFormatElem );
1333  }
1334 
1335  // no parameters on custom hrefUrl, because should link directly to graphic
1336  if ( customHrefString.isEmpty() )
1337  {
1338  QString layerName = currentLayer->name();
1339  if ( QgsServerProjectUtils::wmsUseLayerIds( *project ) )
1340  layerName = currentLayer->id();
1341  else if ( !currentLayer->shortName().isEmpty() )
1342  layerName = currentLayer->shortName();
1343  QUrl mapUrl( hrefString );
1344  QUrlQuery mapUrlQuery( mapUrl.query() );
1345  mapUrlQuery.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WMS" ) );
1346  mapUrlQuery.addQueryItem( QStringLiteral( "VERSION" ), request.wmsParameters().version() );
1347  mapUrlQuery.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetLegendGraphic" ) );
1348  mapUrlQuery.addQueryItem( QStringLiteral( "LAYER" ), layerName );
1349  mapUrlQuery.addQueryItem( QStringLiteral( "FORMAT" ), QStringLiteral( "image/png" ) );
1350  mapUrlQuery.addQueryItem( QStringLiteral( "STYLE" ), styleNameText.data() );
1351  if ( request.wmsParameters().version() == QLatin1String( "1.3.0" ) )
1352  {
1353  mapUrlQuery.addQueryItem( QStringLiteral( "SLD_VERSION" ), QStringLiteral( "1.1.0" ) );
1354  }
1355  mapUrl.setQuery( mapUrlQuery );
1356  customHrefString = mapUrl.toString();
1357  }
1358 
1359  QDomElement getLayerLegendGraphicORElem = doc.createElement( QStringLiteral( "OnlineResource" ) );
1360  getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1361  getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
1362  getLayerLegendGraphicORElem.setAttribute( QStringLiteral( "xlink:href" ), customHrefString );
1363  getLayerLegendGraphicElem.appendChild( getLayerLegendGraphicORElem );
1364  styleElem.appendChild( getLayerLegendGraphicElem );
1365 
1366  layerElem.appendChild( styleElem );
1367  }
1368  }
1369 
1370  void appendCrsElementsToLayer( QDomDocument &doc, QDomElement &layerElement,
1371  const QStringList &crsList, const QStringList &constrainedCrsList )
1372  {
1373  if ( layerElement.isNull() )
1374  {
1375  return;
1376  }
1377 
1378  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1379 
1380  //insert the CRS elements after the title element to be in accordance with the WMS 1.3 specification
1381  QDomElement titleElement = layerElement.firstChildElement( QStringLiteral( "Title" ) );
1382  QDomElement abstractElement = layerElement.firstChildElement( QStringLiteral( "Abstract" ) );
1383  QDomElement keywordListElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1384  QDomElement CRSPrecedingElement = !keywordListElement.isNull() ? keywordListElement : !abstractElement.isNull() ? abstractElement : titleElement;
1385 
1386  if ( CRSPrecedingElement.isNull() )
1387  {
1388  // keyword list element is never empty
1389  const QDomElement keyElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1390  CRSPrecedingElement = keyElement;
1391  }
1392 
1393  //In case the number of advertised CRS is constrained
1394  if ( !constrainedCrsList.isEmpty() )
1395  {
1396  for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1397  {
1398  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, constrainedCrsList.at( i ) );
1399  }
1400  }
1401  else //no crs constraint
1402  {
1403  for ( const QString &crs : crsList )
1404  {
1405  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, crs );
1406  }
1407  }
1408 
1409  // Support for CRS:84 is mandatory (equals EPSG:4326 with reversed axis)
1410  // https://github.com/opengeospatial/ets-wms13/blob/47155399c09b200cb21382874fdb21d5fae4ab6e/src/site/markdown/index.md
1411  if ( version == QLatin1String( "1.3.0" ) )
1412  {
1413  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, QString( "CRS:84" ) );
1414  }
1415  }
1416 
1417  void appendCrsElementToLayer( QDomDocument &doc, QDomElement &layerElement, const QDomElement &precedingElement,
1418  const QString &crsText )
1419  {
1420  if ( crsText.isEmpty() )
1421  return;
1422  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1423  QDomElement crsElement = doc.createElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1424  QDomText crsTextNode = doc.createTextNode( crsText );
1425  crsElement.appendChild( crsTextNode );
1426  layerElement.insertAfter( crsElement, precedingElement );
1427  }
1428 
1429  void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent,
1430  const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList,
1431  const QStringList &constrainedCrsList, const QgsProject *project,
1432  const QgsRectangle &lGeoExtent )
1433  {
1434  if ( layerElem.isNull() )
1435  {
1436  return;
1437  }
1438 
1439  QgsRectangle layerExtent = lExtent;
1440  if ( qgsDoubleNear( layerExtent.xMinimum(), layerExtent.xMaximum() ) || qgsDoubleNear( layerExtent.yMinimum(), layerExtent.yMaximum() ) )
1441  {
1442  //layer bbox cannot be empty
1443  layerExtent.grow( 0.000001 );
1444  }
1445 
1446  QgsRectangle wgs84BoundingRect = lGeoExtent;
1447  if ( wgs84BoundingRect.isNull() )
1448  {
1450 
1451  //transform the layers native CRS into WGS84
1452  if ( !layerExtent.isNull() )
1453  {
1454  QgsCoordinateTransform exGeoTransform( layerCRS, wgs84, project );
1455  try
1456  {
1457  wgs84BoundingRect = exGeoTransform.transformBoundingBox( layerExtent );
1458  }
1459  catch ( const QgsCsException &cse )
1460  {
1461  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1462  wgs84BoundingRect = QgsRectangle();
1463  }
1464  }
1465  }
1466 
1467  if ( qgsDoubleNear( wgs84BoundingRect.xMinimum(), wgs84BoundingRect.xMaximum() ) || qgsDoubleNear( wgs84BoundingRect.yMinimum(), wgs84BoundingRect.yMaximum() ) )
1468  {
1469  //layer bbox cannot be empty
1470  wgs84BoundingRect.grow( 0.000001 );
1471  }
1472 
1473  //Ex_GeographicBoundingBox
1474  QDomElement ExGeoBBoxElement;
1475  const int wgs84precision = 6;
1476  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1477  if ( version == QLatin1String( "1.1.1" ) ) // WMS Version 1.1.1
1478  {
1479  ExGeoBBoxElement = doc.createElement( QStringLiteral( "LatLonBoundingBox" ) );
1480  ExGeoBBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1481  ExGeoBBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1482  ExGeoBBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1483  ExGeoBBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1484  }
1485  else // WMS Version 1.3.0
1486  {
1487  ExGeoBBoxElement = doc.createElement( QStringLiteral( "EX_GeographicBoundingBox" ) );
1488  QDomElement wBoundLongitudeElement = doc.createElement( QStringLiteral( "westBoundLongitude" ) );
1489  QDomText wBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1490  wBoundLongitudeElement.appendChild( wBoundLongitudeText );
1491  ExGeoBBoxElement.appendChild( wBoundLongitudeElement );
1492  QDomElement eBoundLongitudeElement = doc.createElement( QStringLiteral( "eastBoundLongitude" ) );
1493  QDomText eBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1494  eBoundLongitudeElement.appendChild( eBoundLongitudeText );
1495  ExGeoBBoxElement.appendChild( eBoundLongitudeElement );
1496  QDomElement sBoundLatitudeElement = doc.createElement( QStringLiteral( "southBoundLatitude" ) );
1497  QDomText sBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1498  sBoundLatitudeElement.appendChild( sBoundLatitudeText );
1499  ExGeoBBoxElement.appendChild( sBoundLatitudeElement );
1500  QDomElement nBoundLatitudeElement = doc.createElement( QStringLiteral( "northBoundLatitude" ) );
1501  QDomText nBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1502  nBoundLatitudeElement.appendChild( nBoundLatitudeText );
1503  ExGeoBBoxElement.appendChild( nBoundLatitudeElement );
1504  }
1505 
1506  if ( !wgs84BoundingRect.isNull() ) //LatLonBoundingBox / Ex_GeographicBounding box is optional
1507  {
1508  QDomElement lastCRSElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1509  if ( !lastCRSElem.isNull() )
1510  {
1511  layerElem.insertAfter( ExGeoBBoxElement, lastCRSElem );
1512  }
1513  else
1514  {
1515  layerElem.appendChild( ExGeoBBoxElement );
1516  }
1517  }
1518 
1519  //In case the number of advertised CRS is constrained
1520  if ( !constrainedCrsList.isEmpty() )
1521  {
1522  for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1523  {
1524  appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, constrainedCrsList.at( i ), project );
1525  }
1526  }
1527  else //no crs constraint
1528  {
1529  for ( const QString &crs : crsList )
1530  {
1531  appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, crs, project );
1532  }
1533  }
1534  }
1535 
1536 
1537  void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent,
1538  const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText,
1539  const QgsProject *project )
1540  {
1541  if ( layerElem.isNull() )
1542  {
1543  return;
1544  }
1545 
1546  if ( crsText.isEmpty() )
1547  {
1548  return;
1549  }
1550 
1551  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1552 
1554 
1555  //transform the layers native CRS into CRS
1556  QgsRectangle crsExtent;
1557  if ( !layerExtent.isNull() )
1558  {
1559  QgsCoordinateTransform crsTransform( layerCRS, crs, project );
1560  try
1561  {
1562  crsExtent = crsTransform.transformBoundingBox( layerExtent );
1563  }
1564  catch ( QgsCsException &cse )
1565  {
1566  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1567  return;
1568  }
1569  }
1570 
1571  if ( crsExtent.isNull() )
1572  {
1573  return;
1574  }
1575 
1576  int precision = 3;
1577  if ( crs.isGeographic() )
1578  {
1579  precision = 6;
1580  }
1581 
1582  //BoundingBox element
1583  QDomElement bBoxElement = doc.createElement( QStringLiteral( "BoundingBox" ) );
1584  if ( crs.isValid() )
1585  {
1586  bBoxElement.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", crs.authid() );
1587  }
1588 
1589  if ( version != QLatin1String( "1.1.1" ) && crs.hasAxisInverted() )
1590  {
1591  crsExtent.invert();
1592  }
1593 
1594  bBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.xMinimum(), precision ), precision ) );
1595  bBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.yMinimum(), precision ), precision ) );
1596  bBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.xMaximum(), precision ), precision ) );
1597  bBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.yMaximum(), precision ), precision ) );
1598 
1599  QDomElement lastBBoxElem = layerElem.lastChildElement( QStringLiteral( "BoundingBox" ) );
1600  if ( !lastBBoxElem.isNull() )
1601  {
1602  layerElem.insertAfter( bBoxElement, lastBBoxElem );
1603  }
1604  else
1605  {
1606  lastBBoxElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "LatLonBoundingBox" : "EX_GeographicBoundingBox" );
1607  if ( !lastBBoxElem.isNull() )
1608  {
1609  layerElem.insertAfter( bBoxElement, lastBBoxElem );
1610  }
1611  else
1612  {
1613  layerElem.appendChild( bBoxElement );
1614  }
1615  }
1616  }
1617 
1618  QgsRectangle layerBoundingBoxInProjectCrs( const QDomDocument &doc, const QDomElement &layerElem,
1619  const QgsProject *project )
1620  {
1621  QgsRectangle BBox;
1622  if ( layerElem.isNull() )
1623  {
1624  return BBox;
1625  }
1626 
1627  //read box coordinates and layer auth. id
1628  QDomElement boundingBoxElem = layerElem.firstChildElement( QStringLiteral( "BoundingBox" ) );
1629  if ( boundingBoxElem.isNull() )
1630  {
1631  return BBox;
1632  }
1633 
1634  double minx, miny, maxx, maxy;
1635  bool conversionOk;
1636  minx = boundingBoxElem.attribute( QStringLiteral( "minx" ) ).toDouble( &conversionOk );
1637  if ( !conversionOk )
1638  {
1639  return BBox;
1640  }
1641  miny = boundingBoxElem.attribute( QStringLiteral( "miny" ) ).toDouble( &conversionOk );
1642  if ( !conversionOk )
1643  {
1644  return BBox;
1645  }
1646  maxx = boundingBoxElem.attribute( QStringLiteral( "maxx" ) ).toDouble( &conversionOk );
1647  if ( !conversionOk )
1648  {
1649  return BBox;
1650  }
1651  maxy = boundingBoxElem.attribute( QStringLiteral( "maxy" ) ).toDouble( &conversionOk );
1652  if ( !conversionOk )
1653  {
1654  return BBox;
1655  }
1656 
1657 
1658  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1659 
1660  //create layer crs
1661  QgsCoordinateReferenceSystem layerCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( boundingBoxElem.attribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" ) );
1662  if ( !layerCrs.isValid() )
1663  {
1664  return BBox;
1665  }
1666 
1667  BBox.setXMinimum( minx );
1668  BBox.setXMaximum( maxx );
1669  BBox.setYMinimum( miny );
1670  BBox.setYMaximum( maxy );
1671 
1672  if ( version != QLatin1String( "1.1.1" ) && layerCrs.hasAxisInverted() )
1673  {
1674  BBox.invert();
1675  }
1676 
1677  //get project crs
1678  QgsCoordinateTransform t( layerCrs, project->crs(), project );
1679 
1680  //transform
1681  try
1682  {
1683  BBox = t.transformBoundingBox( BBox );
1684  }
1685  catch ( const QgsCsException &cse )
1686  {
1687  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1688  BBox = QgsRectangle();
1689  }
1690 
1691  return BBox;
1692  }
1693 
1694  bool crsSetFromLayerElement( const QDomElement &layerElement, QSet<QString> &crsSet )
1695  {
1696  if ( layerElement.isNull() )
1697  {
1698  return false;
1699  }
1700 
1701  crsSet.clear();
1702 
1703  QDomNodeList crsNodeList;
1704  crsNodeList = layerElement.elementsByTagName( QStringLiteral( "CRS" ) ); // WMS 1.3.0
1705  for ( int i = 0; i < crsNodeList.size(); ++i )
1706  {
1707  crsSet.insert( crsNodeList.at( i ).toElement().text() );
1708  }
1709 
1710  crsNodeList = layerElement.elementsByTagName( QStringLiteral( "SRS" ) ); // WMS 1.1.1
1711  for ( int i = 0; i < crsNodeList.size(); ++i )
1712  {
1713  crsSet.insert( crsNodeList.at( i ).toElement().text() );
1714  }
1715 
1716  return true;
1717  }
1718 
1719  void combineExtentAndCrsOfGroupChildren( QDomDocument &doc, QDomElement &groupElem, const QgsProject *project,
1720  bool considerMapExtent )
1721  {
1722  QgsRectangle combinedBBox;
1723  QSet<QString> combinedCRSSet;
1724  bool firstBBox = true;
1725  bool firstCRSSet = true;
1726 
1727  QDomNodeList layerChildren = groupElem.childNodes();
1728  for ( int j = 0; j < layerChildren.size(); ++j )
1729  {
1730  QDomElement childElem = layerChildren.at( j ).toElement();
1731 
1732  if ( childElem.tagName() != QLatin1String( "Layer" ) )
1733  continue;
1734 
1735  QgsRectangle bbox = layerBoundingBoxInProjectCrs( doc, childElem, project );
1736  if ( bbox.isNull() )
1737  {
1738  continue;
1739  }
1740 
1741  if ( !bbox.isEmpty() )
1742  {
1743  if ( firstBBox )
1744  {
1745  combinedBBox = bbox;
1746  firstBBox = false;
1747  }
1748  else
1749  {
1750  combinedBBox.combineExtentWith( bbox );
1751  }
1752  }
1753 
1754  //combine crs set
1755  QSet<QString> crsSet;
1756  if ( crsSetFromLayerElement( childElem, crsSet ) )
1757  {
1758  if ( firstCRSSet )
1759  {
1760  combinedCRSSet = crsSet;
1761  firstCRSSet = false;
1762  }
1763  else
1764  {
1765  combinedCRSSet.intersect( crsSet );
1766  }
1767  }
1768  }
1769 
1770  QStringList outputCrsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
1771  appendCrsElementsToLayer( doc, groupElem, qgis::setToList( combinedCRSSet ), outputCrsList );
1772 
1773  QgsCoordinateReferenceSystem groupCRS = project->crs();
1774  if ( considerMapExtent )
1775  {
1776  QgsRectangle mapRect = QgsServerProjectUtils::wmsExtent( *project );
1777  if ( !mapRect.isEmpty() )
1778  {
1779  combinedBBox = mapRect;
1780  }
1781  }
1782  appendLayerBoundingBoxes( doc, groupElem, combinedBBox, groupCRS, qgis::setToList( combinedCRSSet ), outputCrsList, project );
1783 
1784  }
1785 
1786  void appendDrawingOrder( QDomDocument &doc, QDomElement &parentElem, QgsServerInterface *serverIface,
1787  const QgsProject *project )
1788  {
1789 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1790  QgsAccessControl *accessControl = serverIface->accessControls();
1791 #else
1792  ( void )serverIface;
1793 #endif
1794  bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
1795  QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
1796 
1797  QStringList layerList;
1798 
1799  const QgsLayerTree *projectLayerTreeRoot = project->layerTreeRoot();
1800  QList< QgsMapLayer * > projectLayerOrder = projectLayerTreeRoot->layerOrder();
1801  for ( int i = 0; i < projectLayerOrder.size(); ++i )
1802  {
1803  QgsMapLayer *l = projectLayerOrder.at( i );
1804 
1805  if ( restrictedLayers.contains( l->name() ) ) //unpublished layer
1806  {
1807  continue;
1808  }
1809 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1810  if ( accessControl && !accessControl->layerReadPermission( l ) )
1811  {
1812  continue;
1813  }
1814 #endif
1815  QString wmsName = l->name();
1816  if ( useLayerIds )
1817  {
1818  wmsName = l->id();
1819  }
1820  else if ( !l->shortName().isEmpty() )
1821  {
1822  wmsName = l->shortName();
1823  }
1824 
1825  layerList << wmsName;
1826  }
1827 
1828  if ( !layerList.isEmpty() )
1829  {
1830  QStringList reversedList;
1831  reversedList.reserve( layerList.size() );
1832  for ( int i = layerList.size() - 1; i >= 0; --i )
1833  reversedList << layerList[ i ];
1834 
1835  QDomElement layerDrawingOrderElem = doc.createElement( QStringLiteral( "LayerDrawingOrder" ) );
1836  QDomText drawingOrderText = doc.createTextNode( reversedList.join( ',' ) );
1837  layerDrawingOrderElem.appendChild( drawingOrderText );
1838  parentElem.appendChild( layerDrawingOrderElem );
1839  }
1840  }
1841 
1842  void appendLayerProjectSettings( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer )
1843  {
1844  if ( !currentLayer )
1845  {
1846  return;
1847  }
1848 
1849  // Layer tree name
1850  QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
1851  QDomText treeNameText = doc.createTextNode( currentLayer->name() );
1852  treeNameElem.appendChild( treeNameText );
1853  layerElem.appendChild( treeNameElem );
1854 
1855  switch ( currentLayer->type() )
1856  {
1858  {
1859  QgsVectorLayer *vLayer = static_cast<QgsVectorLayer *>( currentLayer );
1860 
1861  int displayFieldIdx = -1;
1862  QString displayField = QStringLiteral( "maptip" );
1863  QgsExpression exp( vLayer->displayExpression() );
1864  if ( exp.isField() )
1865  {
1866  displayField = static_cast<const QgsExpressionNodeColumnRef *>( exp.rootNode() )->name();
1867  displayFieldIdx = vLayer->fields().lookupField( displayField );
1868  }
1869 
1870  //attributes
1871  QDomElement attributesElem = doc.createElement( QStringLiteral( "Attributes" ) );
1872  const QgsFields layerFields = vLayer->fields();
1873  for ( int idx = 0; idx < layerFields.count(); ++idx )
1874  {
1875  QgsField field = layerFields.at( idx );
1877  {
1878  continue;
1879  }
1880  // field alias in case of displayField
1881  if ( idx == displayFieldIdx )
1882  {
1883  displayField = vLayer->attributeDisplayName( idx );
1884  }
1885  QDomElement attributeElem = doc.createElement( QStringLiteral( "Attribute" ) );
1886  attributeElem.setAttribute( QStringLiteral( "name" ), field.name() );
1887  attributeElem.setAttribute( QStringLiteral( "type" ), QVariant::typeToName( field.type() ) );
1888  attributeElem.setAttribute( QStringLiteral( "typeName" ), field.typeName() );
1889  QString alias = field.alias();
1890  if ( !alias.isEmpty() )
1891  {
1892  attributeElem.setAttribute( QStringLiteral( "alias" ), alias );
1893  }
1894 
1895  //edit type to text
1896  attributeElem.setAttribute( QStringLiteral( "editType" ), vLayer->editorWidgetSetup( idx ).type() );
1897  attributeElem.setAttribute( QStringLiteral( "comment" ), field.comment() );
1898  attributeElem.setAttribute( QStringLiteral( "length" ), field.length() );
1899  attributeElem.setAttribute( QStringLiteral( "precision" ), field.precision() );
1900  attributesElem.appendChild( attributeElem );
1901  }
1902 
1903  //displayfield
1904  layerElem.setAttribute( QStringLiteral( "displayField" ), displayField );
1905 
1906  //primary key
1907  QgsAttributeList pkAttributes = vLayer->primaryKeyAttributes();
1908  if ( pkAttributes.size() > 0 )
1909  {
1910  QDomElement pkElem = doc.createElement( QStringLiteral( "PrimaryKey" ) );
1911  QgsAttributeList::const_iterator pkIt = pkAttributes.constBegin();
1912  for ( ; pkIt != pkAttributes.constEnd(); ++pkIt )
1913  {
1914  QDomElement pkAttributeElem = doc.createElement( QStringLiteral( "PrimaryKeyAttribute" ) );
1915  QDomText pkAttName = doc.createTextNode( layerFields.at( *pkIt ).name() );
1916  pkAttributeElem.appendChild( pkAttName );
1917  pkElem.appendChild( pkAttributeElem );
1918  }
1919  layerElem.appendChild( pkElem );
1920  }
1921 
1922  //geometry type
1923  layerElem.setAttribute( QStringLiteral( "geometryType" ), QgsWkbTypes::displayString( vLayer->wkbType() ) );
1924 
1925  //opacity
1926  layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( vLayer->opacity() ) );
1927 
1928  layerElem.appendChild( attributesElem );
1929  break;
1930  }
1931 
1933  {
1934  const QgsDataProvider *provider = currentLayer->dataProvider();
1935  if ( provider && provider->name() == "wms" )
1936  {
1937  //advertise as web map background layer
1938  QVariant wmsBackgroundLayer = currentLayer->customProperty( QStringLiteral( "WMSBackgroundLayer" ), false );
1939  QDomElement wmsBackgroundLayerElem = doc.createElement( "WMSBackgroundLayer" );
1940  QDomText wmsBackgroundLayerText = doc.createTextNode( wmsBackgroundLayer.toBool() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1941  wmsBackgroundLayerElem.appendChild( wmsBackgroundLayerText );
1942  layerElem.appendChild( wmsBackgroundLayerElem );
1943 
1944  //publish datasource
1945  QVariant wmsPublishDataSourceUrl = currentLayer->customProperty( QStringLiteral( "WMSPublishDataSourceUrl" ), false );
1946  if ( wmsPublishDataSourceUrl.toBool() )
1947  {
1948  bool tiled = qobject_cast< const QgsRasterDataProvider * >( provider )
1949  ? !qobject_cast< const QgsRasterDataProvider * >( provider )->nativeResolutions().isEmpty()
1950  : false;
1951 
1952  QDomElement dataSourceElem = doc.createElement( tiled ? QStringLiteral( "WMTSDataSource" ) : QStringLiteral( "WMSDataSource" ) );
1953  QDomText dataSourceUri = doc.createTextNode( provider->dataSourceUri() );
1954  dataSourceElem.appendChild( dataSourceUri );
1955  layerElem.appendChild( dataSourceElem );
1956  }
1957  }
1958 
1959  QVariant wmsPrintLayer = currentLayer->customProperty( QStringLiteral( "WMSPrintLayer" ) );
1960  if ( wmsPrintLayer.isValid() )
1961  {
1962  QDomElement wmsPrintLayerElem = doc.createElement( "WMSPrintLayer" );
1963  QDomText wmsPrintLayerText = doc.createTextNode( wmsPrintLayer.toString() );
1964  wmsPrintLayerElem.appendChild( wmsPrintLayerText );
1965  layerElem.appendChild( wmsPrintLayerElem );
1966  }
1967 
1968  //opacity
1969  QgsRasterLayer *rl = static_cast<QgsRasterLayer *>( currentLayer );
1970  QgsRasterRenderer *rasterRenderer = rl->renderer();
1971  if ( rasterRenderer )
1972  {
1973  layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( rasterRenderer->opacity() ) );
1974  }
1975  break;
1976  }
1977 
1984  break;
1985  }
1986  }
1987 
1988  void addKeywordListElement( const QgsProject *project, QDomDocument &doc, QDomElement &parent )
1989  {
1990  bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
1991 
1992  QDomElement keywordsElem = doc.createElement( QStringLiteral( "KeywordList" ) );
1993  //add default keyword
1994  QDomElement keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
1995  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "ISO" ) );
1996  QDomText keywordText = doc.createTextNode( QStringLiteral( "infoMapAccessService" ) );
1997  keywordElem.appendChild( keywordText );
1998  keywordsElem.appendChild( keywordElem );
1999  parent.appendChild( keywordsElem );
2000  QStringList keywords = QgsServerProjectUtils::owsServiceKeywords( *project );
2001  for ( const QString &keyword : std::as_const( keywords ) )
2002  {
2003  if ( !keyword.isEmpty() )
2004  {
2005  keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
2006  keywordText = doc.createTextNode( keyword );
2007  keywordElem.appendChild( keywordText );
2008  if ( sia2045 )
2009  {
2010  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "SIA_Geo405" ) );
2011  }
2012  keywordsElem.appendChild( keywordElem );
2013  }
2014  }
2015  parent.appendChild( keywordsElem );
2016  }
2017  }
2018 
2019  bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers )
2020  {
2021  if ( childNode->nodeType() == QgsLayerTreeNode::NodeGroup )
2022  {
2023  for ( int j = 0; j < childNode->children().size(); ++j )
2024  {
2025  if ( hasQueryableChildren( childNode->children().at( j ), wmsRestrictedLayers ) )
2026  return true;
2027  }
2028  return false;
2029  }
2030  else if ( childNode->nodeType() == QgsLayerTreeNode::NodeLayer )
2031  {
2032  const auto treeLayer { static_cast<const QgsLayerTreeLayer *>( childNode ) };
2033  const auto l { treeLayer->layer() };
2034  if ( l )
2035  {
2036  return ! wmsRestrictedLayers.contains( l->name() ) && l->flags().testFlag( QgsMapLayer::Identifiable );
2037  }
2038  else
2039  {
2040  QgsMessageLog::logMessage( QStringLiteral( "Broken/corrupted layer tree, layer '%1' does not exist: check your project!" ).arg( treeLayer->name() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
2041  }
2042  }
2043  return false;
2044  }
2045 
2046 
2047 } // namespace QgsWms
A helper class that centralizes restrictions given by all the access control filter plugins.
bool layerReadPermission(const QgsMapLayer *layer) const
Returns the layer read right.
bool fillCacheKey(QStringList &cacheKey) const
Fill the capabilities caching key.
A cache for capabilities xml documents (by configuration file path)
const QDomDocument * searchCapabilitiesDocument(const QString &configFilePath, const QString &key)
Returns cached capabilities document (or 0 if document for configuration file not in cache)
void insertCapabilitiesDocument(const QString &configFilePath, const QString &key, const QDomDocument *doc)
Inserts new capabilities document (creates a copy of the document, does not take ownership)
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Abstract base class for spatial data provider implementations.
virtual QString name() const =0
Returns a provider name.
virtual QString dataSourceUri(bool expandAuthConfig=false) const
Gets the data source specification.
QString what() const
Definition: qgsexception.h:48
An expression node which takes it value from a feature's field.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString typeName() const
Gets the field type.
Definition: qgsfield.cpp:139
QString name
Definition: qgsfield.h:60
int precision
Definition: qgsfield.h:57
int length
Definition: qgsfield.h:56
ConfigurationFlags configurationFlags
Definition: qgsfield.h:64
QVariant::Type type
Definition: qgsfield.h:58
QString alias
Definition: qgsfield.h:61
QString comment
Definition: qgsfield.h:59
@ HideFromWms
Field is not available if layer is served as WMS from QGIS server.
Container of fields for a vector layer.
Definition: qgsfields.h:45
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
Layer tree group node serves as a container for layers and further groups.
QString name() const override
Returns the group's name.
bool isMutuallyExclusive() const
Returns whether the group is mutually exclusive (only one child can be checked at a time)
Layer tree node points to a map layer.
QString name() const override
Returns the layer's name.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
This class is a base class for nodes in a layer tree.
@ NodeGroup
Container of other groups and layers.
@ NodeLayer
Leaf node pointing to a layer.
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well)
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
NodeType nodeType() const
Find out about type of the node. It is usually shorter to use convenience functions from QgsLayerTree...
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
bool itemVisibilityChecked() const
Returns whether a node is checked (independently of its ancestors or children)
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:33
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Class used to render QgsLayout as an atlas, by iterating over the features from an associated vector ...
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
bool enabled() const
Returns whether the atlas generation is enabled.
A layout multiframe subclass for HTML content.
A layout item subclass for text labels.
Layout graphical items for displaying a map.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
QString id() const
Returns the item's ID name.
QList< QgsPrintLayout * > printLayouts() const
Returns a list of all print layouts contained in the manager.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
double length() const
Returns the length of the measurement.
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
int pageCount() const
Returns the number of pages in the collection.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:122
void layoutObjects(QList< T * > &objectList) const
Returns a list of layout objects (items and multiframes) of a specific type.
Definition: qgslayout.h:141
QgsLayoutMeasurement convertFromLayoutUnits(double length, QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout's native units to a specified target unit.
Definition: qgslayout.cpp:344
Manages QGIS Server properties for a map layer.
QStringList styles() const
Returns list of all defined style names.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
QString legendUrlFormat() const
Returns the format for a URL based layer legend.
Definition: qgsmaplayer.h:1284
QgsRectangle wgs84Extent(bool forceRecalculate=false) const
Returns the WGS84 extent (EPSG:4326) of the layer according to ReadFlag::FlagTrustLayerMetadata.
virtual QgsRectangle extent() const
Returns the extent of the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QString attribution() const
Returns the attribution of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:400
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QString abstract() const
Returns the abstract of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:326
QString dataUrlFormat() const
Returns the DataUrl format of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:380
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Definition: qgsmaplayer.h:426
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
QString title() const
Returns the title of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:310
QString dataUrl() const
Returns the DataUrl of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:362
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:145
QString attributionUrl() const
Returns the attribution URL of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:418
double minimumScale() const
Returns the minimum map scale (i.e.
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
QString legendUrl() const
Returns the URL for the layer's legend.
Definition: qgsmaplayer.h:1274
double opacity
Definition: qgsmaplayer.h:82
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
double maximumScale() const
Returns the maximum map scale (i.e.
QString keywordList() const
Returns the keyword list of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:342
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Print layout, a QgsLayout subclass for static or atlas-based layouts.
QgsLayoutAtlas * atlas()
Returns the print layout's atlas.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
QString title() const
Returns the project's title.
Definition: qgsproject.cpp:491
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:107
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:106
Represents a raster layer.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
Raster renderer pipe that applies colors to a raster.
double opacity() const
Returns the opacity for the renderer, where opacity is a value between 0 (totally transparent) and 1....
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
void invert()
Swap x/y coordinates in the rectangle.
Definition: qgsrectangle.h:575
A helper class that centralizes caches accesses given by all the server cache filter plugins.
bool setCachedDocument(const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Updates or inserts the document in cache like capabilities.
bool getCachedDocument(QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl) const
Returns cached document (or 0 if document not in cache) like capabilities.
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins.
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
virtual QgsServerCacheManager * cacheManager() const =0
Gets the registered server cache filters.
virtual QString configFilePath()=0
Returns the configuration file path.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
virtual QgsCapabilitiesCache * capabilitiesCache()=0
Gets pointer to the capabiblities cache.
QList< QgsServerMetadataUrlProperties::MetadataUrl > metadataUrls() const
Returns a list of metadataUrl resources associated for the layer.
QString service() const
Returns SERVICE parameter as a string or an empty string if not defined.
QgsServerParameters serverParameters() const
Returns parameters.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
Provides a way to retrieve settings by prioritizing according to environment variables,...
bool getPrintDisabled() const
Returns true if WMS GetPrint request is disabled and the project's reading flag QgsProject::ReadFlag:...
const QList< QgsServerWmsDimensionProperties::WmsDimensionInfo > wmsDimensions() const
Returns the QGIS Server WMS Dimension list.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString displayExpression
QgsEditorWidgetSetup editorWidgetSetup(int index) const
The editor widget setup defines which QgsFieldFormatter and editor widget will be used for the field ...
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
QString version() const override
Returns VERSION parameter as a string or an empty string if not defined.
Class defining request interface passed to WMS service.
Definition: qgswmsrequest.h:35
const QgsWmsParameters & wmsParameters() const
Returns the parameters interpreted for the WMS service.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
SERVER_EXPORT QString wmsRootName(const QgsProject &project)
Returns the WMS root layer name defined in a QGIS project.
SERVER_EXPORT bool wmsInfoFormatSia2045(const QgsProject &project)
Returns if the info format is SIA20145.
SERVER_EXPORT QString wmsInspireMetadataUrl(const QgsProject &project)
Returns the Inspire metadata URL.
SERVER_EXPORT double ceilWithPrecision(double number, int places)
Returns a double greater than number to the specified number of places.
SERVER_EXPORT QStringList wmsRestrictedComposers(const QgsProject &project)
Returns the restricted composer list.
SERVER_EXPORT QgsRectangle wmsExtent(const QgsProject &project)
Returns the WMS Extent restriction.
SERVER_EXPORT bool wmsUseLayerIds(const QgsProject &project)
Returns if layer ids are used as name in WMS.
SERVER_EXPORT QString owsServiceAccessConstraints(const QgsProject &project)
Returns the owsService access constraints defined in project.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
SERVER_EXPORT QString owsServiceOnlineResource(const QgsProject &project)
Returns the owsService online resource defined in project.
SERVER_EXPORT QString owsServiceFees(const QgsProject &project)
Returns the owsService fees defined in project.
SERVER_EXPORT QStringList owsServiceKeywords(const QgsProject &project)
Returns the owsService keywords defined in project.
SERVER_EXPORT QString owsServiceContactPosition(const QgsProject &project)
Returns the owsService contact position defined in project.
SERVER_EXPORT QString serviceUrl(const QString &service, const QgsServerRequest &request, const QgsServerSettings &settings)
Returns the service url defined in the environment variable or with HTTP header.
SERVER_EXPORT QString wmsInspireTemporalReference(const QgsProject &project)
Returns the Inspire temporal reference.
SERVER_EXPORT QStringList wmsOutputCrsList(const QgsProject &project)
Returns the WMS output CRS list.
SERVER_EXPORT QString wmsInspireMetadataDate(const QgsProject &project)
Returns the Inspire metadata date.
SERVER_EXPORT QString owsServiceContactOrganization(const QgsProject &project)
Returns the owsService contact organization defined in project.
SERVER_EXPORT QStringList wmsRestrictedLayers(const QgsProject &project)
Returns the restricted layer name list.
SERVER_EXPORT QString wmsInspireLanguage(const QgsProject &project)
Returns the Inspire language.
SERVER_EXPORT QString wmsInspireMetadataUrlType(const QgsProject &project)
Returns the Inspire metadata URL type.
SERVER_EXPORT bool wmsInspireActivate(const QgsProject &project)
Returns if Inspire is activated.
SERVER_EXPORT int wmsMaxWidth(const QgsProject &project)
Returns the maximum width for WMS images defined in a QGIS project.
SERVER_EXPORT QString owsServiceTitle(const QgsProject &project)
Returns the owsService title defined in project.
SERVER_EXPORT QString owsServiceContactMail(const QgsProject &project)
Returns the owsService contact mail defined in project.
SERVER_EXPORT QString owsServiceAbstract(const QgsProject &project)
Returns the owsService abstract defined in project.
SERVER_EXPORT double floorWithPrecision(double number, int places)
Returns a double less than number to the specified number of places.
SERVER_EXPORT int wmsMaxHeight(const QgsProject &project)
Returns the maximum height for WMS images defined in a QGIS project.
SERVER_EXPORT QString owsServiceContactPhone(const QgsProject &project)
Returns the owsService contact phone defined in project.
SERVER_EXPORT QString owsServiceContactPerson(const QgsProject &project)
Returns the owsService contact person defined in project.
Median cut implementation.
QDomElement getWFSLayersElement(QDomDocument &doc, const QgsProject *project)
Create WFSLayers element for get capabilities document.
void writeGetCapabilities(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response, bool projectSettings)
Output GetCapabilities response.
QDomElement getLayersAndStylesCapabilitiesElement(QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings)
Create element for get capabilities document.
QDomElement getInspireCapabilitiesElement(QDomDocument &doc, const QgsProject *project)
Create InspireCapabilities element for get capabilities document.
QDomElement getComposerTemplatesElement(QDomDocument &doc, const QgsProject *project)
Create ComposerTemplates element for get capabilities document.
QDomElement getServiceElement(QDomDocument &doc, const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *serverSettings)
Create Service element for get capabilities document.
QDomElement getCapabilityElement(QDomDocument &doc, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings, QgsServerInterface *serverIface)
Create Capability element for get capabilities document.
bool hasQueryableChildren(const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers)
QDomDocument getCapabilities(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings)
Creates the WMS GetCapabilities XML document.
QUrl serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Returns WMS service URL.
Definition: qgswmsutils.cpp:33
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:1530
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
CONSTLATIN1STRING geoEpsgCrsAuthId()
Geographic coord sys from EPSG authority.
Definition: qgis.h:1977
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.