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