Skip to content

Commit

Permalink
Merge pull request qgis#55302 from tomass/master
Browse files Browse the repository at this point in the history
WMS service: option to skip name attribute for groups in wms capabilities document
  • Loading branch information
elpaso authored Nov 28, 2023
2 parents bc7f89e + 7d5d262 commit fae9f85
Show file tree
Hide file tree
Showing 11 changed files with 1,747 additions and 12 deletions.
11 changes: 11 additions & 0 deletions python/server/auto_generated/qgsserverprojectutils.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,17 @@ Returns if legend groups should be in the legend graphic response if GetLegendGr
:param project: the QGIS project

:return: if the GetLegendGraphic response has to contain legend groups
%End

bool wmsSkipNameForGroup( const QgsProject &project );
%Docstring
Returns if name attribute should be skipped for groups in WMS capabilities document.

:param project: the QGIS project

:return: if name attribute should be skipped for groups in capabilities

.. versionadded:: 3.36
%End

int wmsFeatureInfoPrecision( const QgsProject &project );
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgsprojectproperties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,8 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa

bool addLayerGroupsLegendGraphic = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMSAddLayerGroupsLegendGraphic" ), QStringLiteral( "/" ) );
mAddLayerGroupsLegendGraphicCheckBox->setChecked( addLayerGroupsLegendGraphic );
bool skipNameForGroup = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMSSkipNameForGroup" ), QStringLiteral( "/" ) );
mSkipNameForGroupCheckBox->setChecked( skipNameForGroup );

bool useLayerIDs = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMSUseLayerIDs" ), QStringLiteral( "/" ) );
mWmsUseLayerIDs->setChecked( useLayerIDs );
Expand Down Expand Up @@ -1554,6 +1556,7 @@ void QgsProjectProperties::apply()
QgsProject::instance()->writeEntry( QStringLiteral( "WMSAddWktGeometry" ), QStringLiteral( "/" ), mAddWktGeometryCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSSegmentizeFeatureInfoGeometry" ), QStringLiteral( "/" ), mSegmentizeFeatureInfoGeometryCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSAddLayerGroupsLegendGraphic" ), QStringLiteral( "/" ), mAddLayerGroupsLegendGraphicCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSSkipNameForGroup" ), QStringLiteral( "/" ), mSkipNameForGroupCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSUseLayerIDs" ), QStringLiteral( "/" ), mWmsUseLayerIDs->isChecked() );

QString maxWidthText = mMaxWidthLineEdit->text();
Expand Down
5 changes: 5 additions & 0 deletions src/server/qgsserverprojectutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ bool QgsServerProjectUtils::wmsAddLegendGroupsLegendGraphic( const QgsProject &p
|| legendGroups.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
}

bool QgsServerProjectUtils::wmsSkipNameForGroup( const QgsProject &project )
{
return project.readBoolEntry( QStringLiteral( "WMSSkipNameForGroup" ), QStringLiteral( "/" ), false );
}

int QgsServerProjectUtils::wmsFeatureInfoPrecision( const QgsProject &project )
{
return project.readNumEntry( QStringLiteral( "WMSPrecision" ), QStringLiteral( "/" ), 6 );
Expand Down
8 changes: 8 additions & 0 deletions src/server/qgsserverprojectutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ namespace QgsServerProjectUtils
*/
SERVER_EXPORT bool wmsAddLegendGroupsLegendGraphic( const QgsProject &project );

/**
* Returns if name attribute should be skipped for groups in WMS capabilities document.
* \param project the QGIS project
* \returns if name attribute should be skipped for groups in capabilities
* \since QGIS 3.36
*/
SERVER_EXPORT bool wmsSkipNameForGroup( const QgsProject &project );

/**
* Returns the geometry precision for GetFeatureInfo request.
* \param project the QGIS project
Expand Down
20 changes: 12 additions & 8 deletions src/server/services/wms/qgswmsgetcapabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,7 @@ namespace QgsWms

bool siaFormat = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
const QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
const bool skipNameForGroup = QgsServerProjectUtils::wmsSkipNameForGroup( *project );

QList< QgsLayerTreeNode * > layerTreeGroupChildren = layerTreeGroup->children();
for ( int i = 0; i < layerTreeGroupChildren.size(); ++i )
Expand Down Expand Up @@ -978,14 +979,17 @@ namespace QgsWms
QString shortName = treeGroupChild->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
QString title = treeGroupChild->customProperty( QStringLiteral( "wmsTitle" ) ).toString();

QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
QDomText nameText;
if ( !shortName.isEmpty() )
nameText = doc.createTextNode( shortName );
else
nameText = doc.createTextNode( name );
nameElem.appendChild( nameText );
layerElem.appendChild( nameElem );
if ( !skipNameForGroup )
{
QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) );
QDomText nameText;
if ( !shortName.isEmpty() )
nameText = doc.createTextNode( shortName );
else
nameText = doc.createTextNode( name );
nameElem.appendChild( nameText );
layerElem.appendChild( nameElem );
}

QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) );
QDomText titleText;
Expand Down
14 changes: 14 additions & 0 deletions src/server/services/wms/qgswmsrendercontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,13 @@ void QgsWmsRenderContext::searchLayersToRenderSld()
}
else if ( mLayerGroups.contains( lname ) )
{
if ( QgsServerProjectUtils::wmsSkipNameForGroup( *mProject ) )
{
QgsWmsParameter param( QgsWmsParameter::LAYER );
param.mValue = lname;
throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined,
param );
}
for ( QgsMapLayer *layer : mLayerGroups[lname] )
{
const QString name = layerNickname( *layer );
Expand Down Expand Up @@ -547,6 +554,13 @@ void QgsWmsRenderContext::searchLayersToRenderStyle()
}
else if ( mLayerGroups.contains( nickname ) )
{
if ( QgsServerProjectUtils::wmsSkipNameForGroup( *mProject ) )
{
QgsWmsParameter param( QgsWmsParameter::LAYER );
param.mValue = nickname;
throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined,
param );
}
// Reverse order of layers from a group
QList<QString> layersFromGroup;
for ( QgsMapLayer *layer : mLayerGroups[nickname] )
Expand Down
18 changes: 14 additions & 4 deletions src/ui/qgsprojectpropertiesbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -2496,7 +2496,7 @@
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<item row="12" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_9">
<item row="1" column="1">
<widget class="QLabel" name="mMaxWidthLabel_2">
Expand Down Expand Up @@ -2557,7 +2557,7 @@
</item>
</layout>
</item>
<item row="10" column="0" colspan="2">
<item row="11" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="mWMSUrlLabel">
Expand Down Expand Up @@ -2598,7 +2598,7 @@
</item>
</layout>
</item>
<item row="9" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<layout class="QHBoxLayout" name="grpWMSPrecision">
<item>
<widget class="QLabel" name="label_5">
Expand Down Expand Up @@ -2864,6 +2864,16 @@
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="mSkipNameForGroupCheckBox">
<property name="toolTip">
<string>GetCapabilities response would skip name attribute for group items while leaving title attribute as is, thus showing the group but making it impossible to include the whole group (rather than it's elements) in subsequent GetMap request LAYERS parameter.</string>
</property>
<property name="text">
<string>Skip name attribute for groups</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="wmts">
Expand Down Expand Up @@ -3343,7 +3353,7 @@
</spacer>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -587,4 +587,5 @@ if (WITH_SERVER)
ADD_PYTHON_TEST(PyQgsServerRequest test_qgsserver_request.py)
ADD_PYTHON_TEST(PyQgsServerResponse test_qgsserver_response.py)
ADD_PYTHON_TEST(PyQgsServerConfigCache test_qgsserver_configcache.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetCapabilitiesGroupName test_qgsserver_wms_getcapabilities_group_name.py)
endif()
79 changes: 79 additions & 0 deletions tests/src/python/test_qgsserver_wms_getcapabilities_group_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""QGIS Unit tests for QgsServer GetCapabilities/GetMap group name attribute exclusion.
From build dir, run: ctest -R PyQgsServerWMSGetCapabilitiesGroupName -V
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Tomas Straupis'
__date__ = '2023-10-13'
__copyright__ = 'Copyright 2023, The QGIS Project'

import os
import urllib.parse

from qgis.testing import unittest
from test_qgsserver import QgsServerTestBase


class TestQgsServerWMSGetCapabilities(QgsServerTestBase):
"""QGIS Server WMS Tests for GetCapabilities/GetMap with group name skipping"""

# Set to True to re-generate reference files for this class
regenerate_reference = False

def setUp(self):
super().setUp()
# First project has "skipping name tag for groups" setting set to false (default)
self.project_with_name = os.path.join(self.testdata_path, "wms_group_test1.qgs")
# Second project has "skipping name tag for groups" setting set to true
self.project_without_name = os.path.join(self.testdata_path, "wms_group_test2.qgs")

def test_wms_getcapabilities_with(self):
r = make_capabilities_request(self, self.project_with_name)
self.assertIn(b'<Name>layer_group</Name>', r, 'attribute <name> should be specified for a layer group')

def test_wms_getmap_with(self):
r = make_map_request(self, self.project_with_name)
self.assertNotIn(b'<ServiceException code="LayerNotDefined">', r, 'there should be no server exception for a layer group in LAYERS=')

def test_wms_getcapabilities_without(self):
r = make_capabilities_request(self, self.project_without_name)
self.assertNotIn(b'<Name>layer_group</Name>', r, 'attribute <name> should NOT be specified for a layer group')

def test_wms_getmap_without(self):
r = make_map_request(self, self.project_without_name)
self.assertIn(b'<ServiceException code="LayerNotDefined">', r, 'there should a server exception for a layer group in LAYERS=')


def make_capabilities_request(instance, project):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(project),
"SERVICE": "WMS",
"REQUEST": "GetCapabilities"
}.items())])
r, h = instance._result(instance._execute_request(qs))
return instance.strip_version_xmlns(r)


def make_map_request(instance, project):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(project),
"SERVICE": "WMS",
"REQUEST": "GetMap",
"BBOX": "6053181%2C594460%2C6053469%2C595183",
"CRS": "EPSG%3A3346",
"WIDTH": "1500",
"HEIGHT": "600",
"LAYERS": "layer_group",
"VERSION": "1.3.0"
}.items())])
r, h = instance._result(instance._execute_request(qs))
return instance.strip_version_xmlns(r)


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit fae9f85

Please sign in to comment.