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