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