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