QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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  //insert the CRS elements after the title element to be in accordance with the WMS 1.3 specification
1379  QDomElement titleElement = layerElement.firstChildElement( QStringLiteral( "Title" ) );
1380  QDomElement abstractElement = layerElement.firstChildElement( QStringLiteral( "Abstract" ) );
1381  QDomElement keywordListElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1382  QDomElement CRSPrecedingElement = !keywordListElement.isNull() ? keywordListElement : !abstractElement.isNull() ? abstractElement : titleElement;
1383 
1384  if ( CRSPrecedingElement.isNull() )
1385  {
1386  // keyword list element is never empty
1387  const QDomElement keyElement = layerElement.firstChildElement( QStringLiteral( "KeywordList" ) );
1388  CRSPrecedingElement = keyElement;
1389  }
1390 
1391  //In case the number of advertised CRS is constrained
1392  if ( !constrainedCrsList.isEmpty() )
1393  {
1394  for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1395  {
1396  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, constrainedCrsList.at( i ) );
1397  }
1398  }
1399  else //no crs constraint
1400  {
1401  for ( const QString &crs : crsList )
1402  {
1403  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, crs );
1404  }
1405  }
1406 
1407  //Support for CRS:84 is mandatory (equals EPSG:4326 with reversed axis)
1408  appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, QString( "CRS:84" ) );
1409  }
1410 
1411  void appendCrsElementToLayer( QDomDocument &doc, QDomElement &layerElement, const QDomElement &precedingElement,
1412  const QString &crsText )
1413  {
1414  if ( crsText.isEmpty() )
1415  return;
1416  QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1417  QDomElement crsElement = doc.createElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1418  QDomText crsTextNode = doc.createTextNode( crsText );
1419  crsElement.appendChild( crsTextNode );
1420  layerElement.insertAfter( crsElement, precedingElement );
1421  }
1422 
1423  void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent,
1424  const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList,
1425  const QStringList &constrainedCrsList, const QgsProject *project,
1426  const QgsRectangle &lGeoExtent )
1427  {
1428  if ( layerElem.isNull() )
1429  {
1430  return;
1431  }
1432 
1433  QgsRectangle layerExtent = lExtent;
1434  if ( qgsDoubleNear( layerExtent.xMinimum(), layerExtent.xMaximum() ) || qgsDoubleNear( layerExtent.yMinimum(), layerExtent.yMaximum() ) )
1435  {
1436  //layer bbox cannot be empty
1437  layerExtent.grow( 0.000001 );
1438  }
1439 
1440  QgsRectangle wgs84BoundingRect = lGeoExtent;
1441  if ( wgs84BoundingRect.isNull() )
1442  {
1444 
1445  //transform the layers native CRS into WGS84
1446  if ( !layerExtent.isNull() )
1447  {
1448  QgsCoordinateTransform exGeoTransform( layerCRS, wgs84, project );
1449  try
1450  {
1451  wgs84BoundingRect = exGeoTransform.transformBoundingBox( layerExtent );
1452  }
1453  catch ( const QgsCsException &cse )
1454  {
1455  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1456  wgs84BoundingRect = QgsRectangle();
1457  }
1458  }
1459  }
1460 
1461  if ( qgsDoubleNear( wgs84BoundingRect.xMinimum(), wgs84BoundingRect.xMaximum() ) || qgsDoubleNear( wgs84BoundingRect.yMinimum(), wgs84BoundingRect.yMaximum() ) )
1462  {
1463  //layer bbox cannot be empty
1464  wgs84BoundingRect.grow( 0.000001 );
1465  }
1466 
1467  //Ex_GeographicBoundingBox
1468  QDomElement ExGeoBBoxElement;
1469  const int wgs84precision = 6;
1470  const QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1471  if ( version == QLatin1String( "1.1.1" ) ) // WMS Version 1.1.1
1472  {
1473  ExGeoBBoxElement = doc.createElement( QStringLiteral( "LatLonBoundingBox" ) );
1474  ExGeoBBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1475  ExGeoBBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1476  ExGeoBBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1477  ExGeoBBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1478  }
1479  else // WMS Version 1.3.0
1480  {
1481  ExGeoBBoxElement = doc.createElement( QStringLiteral( "EX_GeographicBoundingBox" ) );
1482  QDomElement wBoundLongitudeElement = doc.createElement( QStringLiteral( "westBoundLongitude" ) );
1483  QDomText wBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.xMinimum(), wgs84precision ), wgs84precision ) );
1484  wBoundLongitudeElement.appendChild( wBoundLongitudeText );
1485  ExGeoBBoxElement.appendChild( wBoundLongitudeElement );
1486  QDomElement eBoundLongitudeElement = doc.createElement( QStringLiteral( "eastBoundLongitude" ) );
1487  QDomText eBoundLongitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.xMaximum(), wgs84precision ), wgs84precision ) );
1488  eBoundLongitudeElement.appendChild( eBoundLongitudeText );
1489  ExGeoBBoxElement.appendChild( eBoundLongitudeElement );
1490  QDomElement sBoundLatitudeElement = doc.createElement( QStringLiteral( "southBoundLatitude" ) );
1491  QDomText sBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( wgs84BoundingRect.yMinimum(), wgs84precision ), wgs84precision ) );
1492  sBoundLatitudeElement.appendChild( sBoundLatitudeText );
1493  ExGeoBBoxElement.appendChild( sBoundLatitudeElement );
1494  QDomElement nBoundLatitudeElement = doc.createElement( QStringLiteral( "northBoundLatitude" ) );
1495  QDomText nBoundLatitudeText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( wgs84BoundingRect.yMaximum(), wgs84precision ), wgs84precision ) );
1496  nBoundLatitudeElement.appendChild( nBoundLatitudeText );
1497  ExGeoBBoxElement.appendChild( nBoundLatitudeElement );
1498  }
1499 
1500  if ( !wgs84BoundingRect.isNull() ) //LatLonBoundingBox / Ex_GeographicBounding box is optional
1501  {
1502  QDomElement lastCRSElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" );
1503  if ( !lastCRSElem.isNull() )
1504  {
1505  layerElem.insertAfter( ExGeoBBoxElement, lastCRSElem );
1506  }
1507  else
1508  {
1509  layerElem.appendChild( ExGeoBBoxElement );
1510  }
1511  }
1512 
1513  //In case the number of advertised CRS is constrained
1514  if ( !constrainedCrsList.isEmpty() )
1515  {
1516  for ( int i = constrainedCrsList.size() - 1; i >= 0; --i )
1517  {
1518  appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, constrainedCrsList.at( i ), project );
1519  }
1520  }
1521  else //no crs constraint
1522  {
1523  for ( const QString &crs : crsList )
1524  {
1525  appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, crs, project );
1526  }
1527  }
1528  }
1529 
1530 
1531  void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent,
1532  const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText,
1533  const QgsProject *project )
1534  {
1535  if ( layerElem.isNull() )
1536  {
1537  return;
1538  }
1539 
1540  if ( crsText.isEmpty() )
1541  {
1542  return;
1543  }
1544 
1545  QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1546 
1548 
1549  //transform the layers native CRS into CRS
1550  QgsRectangle crsExtent;
1551  if ( !layerExtent.isNull() )
1552  {
1553  QgsCoordinateTransform crsTransform( layerCRS, crs, project );
1554  try
1555  {
1556  crsExtent = crsTransform.transformBoundingBox( layerExtent );
1557  }
1558  catch ( QgsCsException &cse )
1559  {
1560  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1561  return;
1562  }
1563  }
1564 
1565  if ( crsExtent.isNull() )
1566  {
1567  return;
1568  }
1569 
1570  int precision = 3;
1571  if ( crs.isGeographic() )
1572  {
1573  precision = 6;
1574  }
1575 
1576  //BoundingBox element
1577  QDomElement bBoxElement = doc.createElement( QStringLiteral( "BoundingBox" ) );
1578  if ( crs.isValid() )
1579  {
1580  bBoxElement.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", crs.authid() );
1581  }
1582 
1583  if ( version != QLatin1String( "1.1.1" ) && crs.hasAxisInverted() )
1584  {
1585  crsExtent.invert();
1586  }
1587 
1588  bBoxElement.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.xMinimum(), precision ), precision ) );
1589  bBoxElement.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( crsExtent.yMinimum(), precision ), precision ) );
1590  bBoxElement.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.xMaximum(), precision ), precision ) );
1591  bBoxElement.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( crsExtent.yMaximum(), precision ), precision ) );
1592 
1593  QDomElement lastBBoxElem = layerElem.lastChildElement( QStringLiteral( "BoundingBox" ) );
1594  if ( !lastBBoxElem.isNull() )
1595  {
1596  layerElem.insertAfter( bBoxElement, lastBBoxElem );
1597  }
1598  else
1599  {
1600  lastBBoxElem = layerElem.lastChildElement( version == QLatin1String( "1.1.1" ) ? "LatLonBoundingBox" : "EX_GeographicBoundingBox" );
1601  if ( !lastBBoxElem.isNull() )
1602  {
1603  layerElem.insertAfter( bBoxElement, lastBBoxElem );
1604  }
1605  else
1606  {
1607  layerElem.appendChild( bBoxElement );
1608  }
1609  }
1610  }
1611 
1612  QgsRectangle layerBoundingBoxInProjectCrs( const QDomDocument &doc, const QDomElement &layerElem,
1613  const QgsProject *project )
1614  {
1615  QgsRectangle BBox;
1616  if ( layerElem.isNull() )
1617  {
1618  return BBox;
1619  }
1620 
1621  //read box coordinates and layer auth. id
1622  QDomElement boundingBoxElem = layerElem.firstChildElement( QStringLiteral( "BoundingBox" ) );
1623  if ( boundingBoxElem.isNull() )
1624  {
1625  return BBox;
1626  }
1627 
1628  double minx, miny, maxx, maxy;
1629  bool conversionOk;
1630  minx = boundingBoxElem.attribute( QStringLiteral( "minx" ) ).toDouble( &conversionOk );
1631  if ( !conversionOk )
1632  {
1633  return BBox;
1634  }
1635  miny = boundingBoxElem.attribute( QStringLiteral( "miny" ) ).toDouble( &conversionOk );
1636  if ( !conversionOk )
1637  {
1638  return BBox;
1639  }
1640  maxx = boundingBoxElem.attribute( QStringLiteral( "maxx" ) ).toDouble( &conversionOk );
1641  if ( !conversionOk )
1642  {
1643  return BBox;
1644  }
1645  maxy = boundingBoxElem.attribute( QStringLiteral( "maxy" ) ).toDouble( &conversionOk );
1646  if ( !conversionOk )
1647  {
1648  return BBox;
1649  }
1650 
1651 
1652  QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );
1653 
1654  //create layer crs
1655  QgsCoordinateReferenceSystem layerCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( boundingBoxElem.attribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS" ) );
1656  if ( !layerCrs.isValid() )
1657  {
1658  return BBox;
1659  }
1660 
1661  BBox.setXMinimum( minx );
1662  BBox.setXMaximum( maxx );
1663  BBox.setYMinimum( miny );
1664  BBox.setYMaximum( maxy );
1665 
1666  if ( version != QLatin1String( "1.1.1" ) && layerCrs.hasAxisInverted() )
1667  {
1668  BBox.invert();
1669  }
1670 
1671  //get project crs
1672  QgsCoordinateTransform t( layerCrs, project->crs(), project );
1673 
1674  //transform
1675  try
1676  {
1677  BBox = t.transformBoundingBox( BBox );
1678  }
1679  catch ( const QgsCsException &cse )
1680  {
1681  QgsMessageLog::logMessage( QStringLiteral( "Error transforming extent: %1" ).arg( cse.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
1682  BBox = QgsRectangle();
1683  }
1684 
1685  return BBox;
1686  }
1687 
1688  bool crsSetFromLayerElement( const QDomElement &layerElement, QSet<QString> &crsSet )
1689  {
1690  if ( layerElement.isNull() )
1691  {
1692  return false;
1693  }
1694 
1695  crsSet.clear();
1696 
1697  QDomNodeList crsNodeList;
1698  crsNodeList = layerElement.elementsByTagName( QStringLiteral( "CRS" ) ); // WMS 1.3.0
1699  for ( int i = 0; i < crsNodeList.size(); ++i )
1700  {
1701  crsSet.insert( crsNodeList.at( i ).toElement().text() );
1702  }
1703 
1704  crsNodeList = layerElement.elementsByTagName( QStringLiteral( "SRS" ) ); // WMS 1.1.1
1705  for ( int i = 0; i < crsNodeList.size(); ++i )
1706  {
1707  crsSet.insert( crsNodeList.at( i ).toElement().text() );
1708  }
1709 
1710  return true;
1711  }
1712 
1713  void combineExtentAndCrsOfGroupChildren( QDomDocument &doc, QDomElement &groupElem, const QgsProject *project,
1714  bool considerMapExtent )
1715  {
1716  QgsRectangle combinedBBox;
1717  QSet<QString> combinedCRSSet;
1718  bool firstBBox = true;
1719  bool firstCRSSet = true;
1720 
1721  QDomNodeList layerChildren = groupElem.childNodes();
1722  for ( int j = 0; j < layerChildren.size(); ++j )
1723  {
1724  QDomElement childElem = layerChildren.at( j ).toElement();
1725 
1726  if ( childElem.tagName() != QLatin1String( "Layer" ) )
1727  continue;
1728 
1729  QgsRectangle bbox = layerBoundingBoxInProjectCrs( doc, childElem, project );
1730  if ( bbox.isNull() )
1731  {
1732  continue;
1733  }
1734 
1735  if ( !bbox.isEmpty() )
1736  {
1737  if ( firstBBox )
1738  {
1739  combinedBBox = bbox;
1740  firstBBox = false;
1741  }
1742  else
1743  {
1744  combinedBBox.combineExtentWith( bbox );
1745  }
1746  }
1747 
1748  //combine crs set
1749  QSet<QString> crsSet;
1750  if ( crsSetFromLayerElement( childElem, crsSet ) )
1751  {
1752  if ( firstCRSSet )
1753  {
1754  combinedCRSSet = crsSet;
1755  firstCRSSet = false;
1756  }
1757  else
1758  {
1759  combinedCRSSet.intersect( crsSet );
1760  }
1761  }
1762  }
1763 
1764  QStringList outputCrsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
1765  appendCrsElementsToLayer( doc, groupElem, qgis::setToList( combinedCRSSet ), outputCrsList );
1766 
1767  QgsCoordinateReferenceSystem groupCRS = project->crs();
1768  if ( considerMapExtent )
1769  {
1770  QgsRectangle mapRect = QgsServerProjectUtils::wmsExtent( *project );
1771  if ( !mapRect.isEmpty() )
1772  {
1773  combinedBBox = mapRect;
1774  }
1775  }
1776  appendLayerBoundingBoxes( doc, groupElem, combinedBBox, groupCRS, qgis::setToList( combinedCRSSet ), outputCrsList, project );
1777 
1778  }
1779 
1780  void appendDrawingOrder( QDomDocument &doc, QDomElement &parentElem, QgsServerInterface *serverIface,
1781  const QgsProject *project )
1782  {
1783 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1784  QgsAccessControl *accessControl = serverIface->accessControls();
1785 #else
1786  ( void )serverIface;
1787 #endif
1788  bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
1789  QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
1790 
1791  QStringList layerList;
1792 
1793  const QgsLayerTree *projectLayerTreeRoot = project->layerTreeRoot();
1794  QList< QgsMapLayer * > projectLayerOrder = projectLayerTreeRoot->layerOrder();
1795  for ( int i = 0; i < projectLayerOrder.size(); ++i )
1796  {
1797  QgsMapLayer *l = projectLayerOrder.at( i );
1798 
1799  if ( restrictedLayers.contains( l->name() ) ) //unpublished layer
1800  {
1801  continue;
1802  }
1803 #ifdef HAVE_SERVER_PYTHON_PLUGINS
1804  if ( accessControl && !accessControl->layerReadPermission( l ) )
1805  {
1806  continue;
1807  }
1808 #endif
1809  QString wmsName = l->name();
1810  if ( useLayerIds )
1811  {
1812  wmsName = l->id();
1813  }
1814  else if ( !l->shortName().isEmpty() )
1815  {
1816  wmsName = l->shortName();
1817  }
1818 
1819  layerList << wmsName;
1820  }
1821 
1822  if ( !layerList.isEmpty() )
1823  {
1824  QStringList reversedList;
1825  reversedList.reserve( layerList.size() );
1826  for ( int i = layerList.size() - 1; i >= 0; --i )
1827  reversedList << layerList[ i ];
1828 
1829  QDomElement layerDrawingOrderElem = doc.createElement( QStringLiteral( "LayerDrawingOrder" ) );
1830  QDomText drawingOrderText = doc.createTextNode( reversedList.join( ',' ) );
1831  layerDrawingOrderElem.appendChild( drawingOrderText );
1832  parentElem.appendChild( layerDrawingOrderElem );
1833  }
1834  }
1835 
1836  void appendLayerProjectSettings( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer )
1837  {
1838  if ( !currentLayer )
1839  {
1840  return;
1841  }
1842 
1843  // Layer tree name
1844  QDomElement treeNameElem = doc.createElement( QStringLiteral( "TreeName" ) );
1845  QDomText treeNameText = doc.createTextNode( currentLayer->name() );
1846  treeNameElem.appendChild( treeNameText );
1847  layerElem.appendChild( treeNameElem );
1848 
1849  switch ( currentLayer->type() )
1850  {
1852  {
1853  QgsVectorLayer *vLayer = static_cast<QgsVectorLayer *>( currentLayer );
1854 
1855  int displayFieldIdx = -1;
1856  QString displayField = QStringLiteral( "maptip" );
1857  QgsExpression exp( vLayer->displayExpression() );
1858  if ( exp.isField() )
1859  {
1860  displayField = static_cast<const QgsExpressionNodeColumnRef *>( exp.rootNode() )->name();
1861  displayFieldIdx = vLayer->fields().lookupField( displayField );
1862  }
1863 
1864  //attributes
1865  QDomElement attributesElem = doc.createElement( QStringLiteral( "Attributes" ) );
1866  const QgsFields layerFields = vLayer->fields();
1867  for ( int idx = 0; idx < layerFields.count(); ++idx )
1868  {
1869  QgsField field = layerFields.at( idx );
1871  {
1872  continue;
1873  }
1874  // field alias in case of displayField
1875  if ( idx == displayFieldIdx )
1876  {
1877  displayField = vLayer->attributeDisplayName( idx );
1878  }
1879  QDomElement attributeElem = doc.createElement( QStringLiteral( "Attribute" ) );
1880  attributeElem.setAttribute( QStringLiteral( "name" ), field.name() );
1881  attributeElem.setAttribute( QStringLiteral( "type" ), QVariant::typeToName( field.type() ) );
1882  attributeElem.setAttribute( QStringLiteral( "typeName" ), field.typeName() );
1883  QString alias = field.alias();
1884  if ( !alias.isEmpty() )
1885  {
1886  attributeElem.setAttribute( QStringLiteral( "alias" ), alias );
1887  }
1888 
1889  //edit type to text
1890  attributeElem.setAttribute( QStringLiteral( "editType" ), vLayer->editorWidgetSetup( idx ).type() );
1891  attributeElem.setAttribute( QStringLiteral( "comment" ), field.comment() );
1892  attributeElem.setAttribute( QStringLiteral( "length" ), field.length() );
1893  attributeElem.setAttribute( QStringLiteral( "precision" ), field.precision() );
1894  attributesElem.appendChild( attributeElem );
1895  }
1896 
1897  //displayfield
1898  layerElem.setAttribute( QStringLiteral( "displayField" ), displayField );
1899 
1900  //primary key
1901  QgsAttributeList pkAttributes = vLayer->primaryKeyAttributes();
1902  if ( pkAttributes.size() > 0 )
1903  {
1904  QDomElement pkElem = doc.createElement( QStringLiteral( "PrimaryKey" ) );
1905  QgsAttributeList::const_iterator pkIt = pkAttributes.constBegin();
1906  for ( ; pkIt != pkAttributes.constEnd(); ++pkIt )
1907  {
1908  QDomElement pkAttributeElem = doc.createElement( QStringLiteral( "PrimaryKeyAttribute" ) );
1909  QDomText pkAttName = doc.createTextNode( layerFields.at( *pkIt ).name() );
1910  pkAttributeElem.appendChild( pkAttName );
1911  pkElem.appendChild( pkAttributeElem );
1912  }
1913  layerElem.appendChild( pkElem );
1914  }
1915 
1916  //geometry type
1917  layerElem.setAttribute( QStringLiteral( "geometryType" ), QgsWkbTypes::displayString( vLayer->wkbType() ) );
1918 
1919  //opacity
1920  layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( vLayer->opacity() ) );
1921 
1922  layerElem.appendChild( attributesElem );
1923  break;
1924  }
1925 
1927  {
1928  const QgsDataProvider *provider = currentLayer->dataProvider();
1929  if ( provider && provider->name() == "wms" )
1930  {
1931  //advertise as web map background layer
1932  QVariant wmsBackgroundLayer = currentLayer->customProperty( QStringLiteral( "WMSBackgroundLayer" ), false );
1933  QDomElement wmsBackgroundLayerElem = doc.createElement( "WMSBackgroundLayer" );
1934  QDomText wmsBackgroundLayerText = doc.createTextNode( wmsBackgroundLayer.toBool() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1935  wmsBackgroundLayerElem.appendChild( wmsBackgroundLayerText );
1936  layerElem.appendChild( wmsBackgroundLayerElem );
1937 
1938  //publish datasource
1939  QVariant wmsPublishDataSourceUrl = currentLayer->customProperty( QStringLiteral( "WMSPublishDataSourceUrl" ), false );
1940  if ( wmsPublishDataSourceUrl.toBool() )
1941  {
1942  bool tiled = qobject_cast< const QgsRasterDataProvider * >( provider )
1943  ? !qobject_cast< const QgsRasterDataProvider * >( provider )->nativeResolutions().isEmpty()
1944  : false;
1945 
1946  QDomElement dataSourceElem = doc.createElement( tiled ? QStringLiteral( "WMTSDataSource" ) : QStringLiteral( "WMSDataSource" ) );
1947  QDomText dataSourceUri = doc.createTextNode( provider->dataSourceUri() );
1948  dataSourceElem.appendChild( dataSourceUri );
1949  layerElem.appendChild( dataSourceElem );
1950  }
1951  }
1952 
1953  QVariant wmsPrintLayer = currentLayer->customProperty( QStringLiteral( "WMSPrintLayer" ) );
1954  if ( wmsPrintLayer.isValid() )
1955  {
1956  QDomElement wmsPrintLayerElem = doc.createElement( "WMSPrintLayer" );
1957  QDomText wmsPrintLayerText = doc.createTextNode( wmsPrintLayer.toString() );
1958  wmsPrintLayerElem.appendChild( wmsPrintLayerText );
1959  layerElem.appendChild( wmsPrintLayerElem );
1960  }
1961 
1962  //opacity
1963  QgsRasterLayer *rl = static_cast<QgsRasterLayer *>( currentLayer );
1964  QgsRasterRenderer *rasterRenderer = rl->renderer();
1965  if ( rasterRenderer )
1966  {
1967  layerElem.setAttribute( QStringLiteral( "opacity" ), QString::number( rasterRenderer->opacity() ) );
1968  }
1969  break;
1970  }
1971 
1977  break;
1978  }
1979  }
1980 
1981  void addKeywordListElement( const QgsProject *project, QDomDocument &doc, QDomElement &parent )
1982  {
1983  bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
1984 
1985  QDomElement keywordsElem = doc.createElement( QStringLiteral( "KeywordList" ) );
1986  //add default keyword
1987  QDomElement keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
1988  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "ISO" ) );
1989  QDomText keywordText = doc.createTextNode( QStringLiteral( "infoMapAccessService" ) );
1990  keywordElem.appendChild( keywordText );
1991  keywordsElem.appendChild( keywordElem );
1992  parent.appendChild( keywordsElem );
1993  QStringList keywords = QgsServerProjectUtils::owsServiceKeywords( *project );
1994  for ( const QString &keyword : std::as_const( keywords ) )
1995  {
1996  if ( !keyword.isEmpty() )
1997  {
1998  keywordElem = doc.createElement( QStringLiteral( "Keyword" ) );
1999  keywordText = doc.createTextNode( keyword );
2000  keywordElem.appendChild( keywordText );
2001  if ( sia2045 )
2002  {
2003  keywordElem.setAttribute( QStringLiteral( "vocabulary" ), QStringLiteral( "SIA_Geo405" ) );
2004  }
2005  keywordsElem.appendChild( keywordElem );
2006  }
2007  }
2008  parent.appendChild( keywordsElem );
2009  }
2010  }
2011 
2012  bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers )
2013  {
2014  if ( childNode->nodeType() == QgsLayerTreeNode::NodeGroup )
2015  {
2016  for ( int j = 0; j < childNode->children().size(); ++j )
2017  {
2018  if ( hasQueryableChildren( childNode->children().at( j ), wmsRestrictedLayers ) )
2019  return true;
2020  }
2021  return false;
2022  }
2023  else if ( childNode->nodeType() == QgsLayerTreeNode::NodeLayer )
2024  {
2025  const auto treeLayer { static_cast<const QgsLayerTreeLayer *>( childNode ) };
2026  const auto l { treeLayer->layer() };
2027  if ( l )
2028  {
2029  return ! wmsRestrictedLayers.contains( l->name() ) && l->flags().testFlag( QgsMapLayer::Identifiable );
2030  }
2031  else
2032  {
2033  QgsMessageLog::logMessage( QStringLiteral( "Broken/corrupted layer tree, layer '%1' does not exist: check your project!" ).arg( treeLayer->name() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
2034  }
2035  }
2036  return false;
2037  }
2038 
2039 
2040 } // namespace QgsWms
A helper class that centralizes restrictions given by all the access control filter plugins.
bool layerReadPermission(const QgsMapLayer *layer) const
Returns the layer read right.
bool fillCacheKey(QStringList &cacheKey) const
Fill the capabilities caching key.
A cache for capabilities xml documents (by configuration file path)
const QDomDocument * searchCapabilitiesDocument(const QString &configFilePath, const QString &key)
Returns cached capabilities document (or 0 if document for configuration file not in cache)
void insertCapabilitiesDocument(const QString &configFilePath, const QString &key, const QDomDocument *doc)
Inserts new capabilities document (creates a copy of the document, does not take ownership)
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
QString authid() const
Returns the authority identifier for the CRS.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Abstract base class for spatial data provider implementations.
virtual QString name() const =0
Returns a provider name.
virtual QString dataSourceUri(bool expandAuthConfig=false) const
Gets the data source specification.
QString what() const
Definition: qgsexception.h:48
An expression node which takes it value from a feature's field.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString typeName() const
Gets the field type.
Definition: qgsfield.cpp:138
QString name
Definition: qgsfield.h:60
int precision
Definition: qgsfield.h:57
int length
Definition: qgsfield.h:56
ConfigurationFlags configurationFlags
Definition: qgsfield.h:64
QVariant::Type type
Definition: qgsfield.h:58
QString alias
Definition: qgsfield.h:61
QString comment
Definition: qgsfield.h:59
@ HideFromWms
Fields is available if layer is served as WMS from QGIS server.
Container of fields for a vector layer.
Definition: qgsfields.h:45
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
Layer tree group node serves as a container for layers and further groups.
QString name() const override
Returns the group's name.
bool isMutuallyExclusive() const
Returns whether the group is mutually exclusive (only one child can be checked at a time)
Layer tree node points to a map layer.
QString name() const override
Returns the layer's name.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
This class is a base class for nodes in a layer tree.
@ NodeGroup
Container of other groups and layers.
@ NodeLayer
Leaf node pointing to a layer.
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well)
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
NodeType nodeType() const
Find out about type of the node. It is usually shorter to use convenience functions from QgsLayerTree...
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
bool itemVisibilityChecked() const
Returns whether a node is checked (independently of its ancestors or children)
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:33
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Class used to render QgsLayout as an atlas, by iterating over the features from an associated vector ...
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
bool enabled() const
Returns whether the atlas generation is enabled.
A layout multiframe subclass for HTML content.
A layout item subclass for text labels.
Layout graphical items for displaying a map.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
QString id() const
Returns the item's ID name.
QList< QgsPrintLayout * > printLayouts() const
Returns a list of all print layouts contained in the manager.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
double length() const
Returns the length of the measurement.
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
int pageCount() const
Returns the number of pages in the collection.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:122
void layoutObjects(QList< T * > &objectList) const
Returns a list of layout objects (items and multiframes) of a specific type.
Definition: qgslayout.h:141
QgsLayoutMeasurement convertFromLayoutUnits(double length, QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout's native units to a specified target unit.
Definition: qgslayout.cpp:344
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:1281
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:397
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:323
QString dataUrlFormat() const
Returns the DataUrl format of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:377
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Definition: qgsmaplayer.h:423
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:307
QString dataUrl() const
Returns the DataUrl of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:359
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:142
QString attributionUrl() const
Returns the attribution URL of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:415
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:1271
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:339
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:488
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
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
SERVER_EXPORT QString wmsRootName(const QgsProject &project)
Returns the WMS root layer name defined in a QGIS project.
SERVER_EXPORT bool wmsInfoFormatSia2045(const QgsProject &project)
Returns if the info format is SIA20145.
SERVER_EXPORT QString wmsInspireMetadataUrl(const QgsProject &project)
Returns the Inspire metadata URL.
SERVER_EXPORT double ceilWithPrecision(double number, int places)
Returns a double greater than number to the specified number of places.
SERVER_EXPORT QStringList wmsRestrictedComposers(const QgsProject &project)
Returns the restricted composer list.
SERVER_EXPORT QgsRectangle wmsExtent(const QgsProject &project)
Returns the WMS Extent restriction.
SERVER_EXPORT bool wmsUseLayerIds(const QgsProject &project)
Returns if layer ids are used as name in WMS.
SERVER_EXPORT QString owsServiceAccessConstraints(const QgsProject &project)
Returns the owsService access constraints defined in project.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
SERVER_EXPORT QString owsServiceOnlineResource(const QgsProject &project)
Returns the owsService online resource defined in project.
SERVER_EXPORT QString owsServiceFees(const QgsProject &project)
Returns the owsService fees defined in project.
SERVER_EXPORT QStringList owsServiceKeywords(const QgsProject &project)
Returns the owsService keywords defined in project.
SERVER_EXPORT QString owsServiceContactPosition(const QgsProject &project)
Returns the owsService contact position defined in project.
SERVER_EXPORT QString serviceUrl(const QString &service, const QgsServerRequest &request, const QgsServerSettings &settings)
Returns the service url defined in the environment variable or with HTTP header.
SERVER_EXPORT QString wmsInspireTemporalReference(const QgsProject &project)
Returns the Inspire temporal reference.
SERVER_EXPORT QStringList wmsOutputCrsList(const QgsProject &project)
Returns the WMS output CRS list.
SERVER_EXPORT QString wmsInspireMetadataDate(const QgsProject &project)
Returns the Inspire metadata date.
SERVER_EXPORT QString owsServiceContactOrganization(const QgsProject &project)
Returns the owsService contact organization defined in project.
SERVER_EXPORT QStringList wmsRestrictedLayers(const QgsProject &project)
Returns the restricted layer name list.
SERVER_EXPORT QString wmsInspireLanguage(const QgsProject &project)
Returns the Inspire language.
SERVER_EXPORT QString wmsInspireMetadataUrlType(const QgsProject &project)
Returns the Inspire metadata URL type.
SERVER_EXPORT bool wmsInspireActivate(const QgsProject &project)
Returns if Inspire is activated.
SERVER_EXPORT int wmsMaxWidth(const QgsProject &project)
Returns the maximum width for WMS images defined in a QGIS project.
SERVER_EXPORT QString owsServiceTitle(const QgsProject &project)
Returns the owsService title defined in project.
SERVER_EXPORT QString owsServiceContactMail(const QgsProject &project)
Returns the owsService contact mail defined in project.
SERVER_EXPORT QString owsServiceAbstract(const QgsProject &project)
Returns the owsService abstract defined in project.
SERVER_EXPORT double floorWithPrecision(double number, int places)
Returns a double less than number to the specified number of places.
SERVER_EXPORT int wmsMaxHeight(const QgsProject &project)
Returns the maximum height for WMS images defined in a QGIS project.
SERVER_EXPORT QString owsServiceContactPhone(const QgsProject &project)
Returns the owsService contact phone defined in project.
SERVER_EXPORT QString owsServiceContactPerson(const QgsProject &project)
Returns the owsService contact person defined in project.
Median cut implementation.
QDomElement getWFSLayersElement(QDomDocument &doc, const QgsProject *project)
Create WFSLayers element for get capabilities document.
void writeGetCapabilities(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, QgsServerResponse &response, bool projectSettings)
Output GetCapabilities response.
QDomElement getLayersAndStylesCapabilitiesElement(QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings)
Create element for get capabilities document.
QDomElement getInspireCapabilitiesElement(QDomDocument &doc, const QgsProject *project)
Create InspireCapabilities element for get capabilities document.
QDomElement getComposerTemplatesElement(QDomDocument &doc, const QgsProject *project)
Create ComposerTemplates element for get capabilities document.
QDomElement getServiceElement(QDomDocument &doc, const QgsProject *project, const QgsWmsRequest &request, const QgsServerSettings *serverSettings)
Create Service element for get capabilities document.
QDomElement getCapabilityElement(QDomDocument &doc, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings, QgsServerInterface *serverIface)
Create Capability element for get capabilities document.
bool hasQueryableChildren(const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers)
QDomDocument getCapabilities(QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, bool projectSettings)
Creates the WMS GetCapabilities XML document.
QUrl serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Returns WMS service URL.
Definition: qgswmsutils.cpp:33
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:1198
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1246
CONSTLATIN1STRING geoEpsgCrsAuthId()
Geographic coord sys from EPSG authority.
Definition: qgis.h:1654
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.