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