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