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