Skip to content

Commit

Permalink
[server] Allow WMS GetFeatureInfo on root layer and groups
Browse files Browse the repository at this point in the history
cherry-picking commit 0e8b90d from 3_6
  • Loading branch information
elpaso committed Apr 5, 2019
1 parent 4ffed0f commit d87bd22
Show file tree
Hide file tree
Showing 16 changed files with 4,605 additions and 14 deletions.
41 changes: 40 additions & 1 deletion src/server/services/wms/qgswmsgetcapabilities.cpp
Expand Up @@ -822,6 +822,15 @@ namespace QgsWms
layerParentElem.appendChild( treeNameElem );
}

if ( hasQueryableChildren( projectLayerTreeRoot, QgsServerProjectUtils::wmsRestrictedLayers( *project ) ) )
{
layerParentElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
}
else
{
layerParentElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
}

appendLayersFromTreeGroup( doc, layerParentElem, serverIface, project, version, request, projectLayerTreeRoot, projectSettings );

combineExtentAndCrsOfGroupChildren( doc, layerParentElem, project, true );
Expand All @@ -843,7 +852,7 @@ namespace QgsWms
{
bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *project );
bool siaFormat = QgsServerProjectUtils::wmsInfoFormatSia2045( *project );
QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );
const QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project );

QList< QgsLayerTreeNode * > layerTreeGroupChildren = layerTreeGroup->children();
for ( int i = 0; i < layerTreeGroupChildren.size(); ++i )
Expand Down Expand Up @@ -910,6 +919,16 @@ namespace QgsWms
layerElem.appendChild( treeNameElem );
}

// Set queryable if any of the children are
if ( hasQueryableChildren( treeNode, restrictedLayers ) )
{
layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "1" ) );
}
else
{
layerElem.setAttribute( QStringLiteral( "queryable" ), QStringLiteral( "0" ) );
}

appendLayersFromTreeGroup( doc, layerElem, serverIface, project, version, request, treeGroupChild, projectSettings );

combineExtentAndCrsOfGroupChildren( doc, layerElem, project );
Expand Down Expand Up @@ -1812,6 +1831,26 @@ namespace QgsWms
}
}

bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers )
{
if ( childNode->nodeType() == QgsLayerTreeNode::NodeGroup )
{
for ( int j = 0; j < childNode->children().size(); ++j )
{
if ( hasQueryableChildren( childNode->children().at( j ), wmsRestrictedLayers ) )
return true;
}
return false;
}
else if ( childNode->nodeType() == QgsLayerTreeNode::NodeLayer )
{
const auto treeLayer { static_cast<const QgsLayerTreeLayer *>( childNode ) };
const auto l { treeLayer->layer() };
return ! wmsRestrictedLayers.contains( l->name() ) && l->flags().testFlag( QgsMapLayer::Identifiable );
}
return false;
}


} // namespace QgsWms

Expand Down
2 changes: 2 additions & 0 deletions src/server/services/wms/qgswmsgetcapabilities.h
Expand Up @@ -83,6 +83,8 @@ namespace QgsWms
QDomDocument getCapabilities( QgsServerInterface *serverIface, const QgsProject *project,
const QString &version, const QgsServerRequest &request,
bool projectSettings );

bool hasQueryableChildren( const QgsLayerTreeNode *childNode, const QStringList &wmsRestrictedLayers );
} // namespace QgsWms

#endif
69 changes: 65 additions & 4 deletions src/server/services/wms/qgswmsrenderer.cpp
Expand Up @@ -886,7 +886,8 @@ namespace QgsWms
{
// Verifying Mandatory parameters
// The QUERY_LAYERS parameter is Mandatory
QStringList queryLayers = mWmsParameters.queryLayersNickname();
QStringList queryLayers = flattenedQueryLayers();

if ( queryLayers.isEmpty() )
{
QString msg = QObject::tr( "QUERY_LAYERS parameter is required for GetFeatureInfo" );
Expand Down Expand Up @@ -1183,7 +1184,8 @@ namespace QgsWms
QDomDocument QgsRenderer::featureInfoDocument( QList<QgsMapLayer *> &layers, const QgsMapSettings &mapSettings,
const QImage *outputImage, const QString &version ) const
{
QStringList queryLayers = mWmsParameters.queryLayersNickname();

const QStringList queryLayers = flattenedQueryLayers( );

bool ijDefined = ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() );

Expand Down Expand Up @@ -1366,8 +1368,40 @@ namespace QgsWms
}
else if ( ( validLayer && !queryableLayer ) || ( !validLayer && mLayerGroups.contains( queryLayer ) ) )
{
QString msg = QObject::tr( "Layer '%1' is not queryable" ).arg( queryLayer );
throw QgsBadRequestException( QStringLiteral( "LayerNotQueryable" ), msg );
auto queryLayerName { queryLayer };
// Check if this layer belongs to a group and the group has any queryable layers
bool hasGroupAndQueryable { false };
if ( ! mWmsParameters.queryLayersNickname().contains( queryLayer ) )
{
// Find which group this layer belongs to
const auto &constNicks { mWmsParameters.queryLayersNickname() };
for ( const auto &ql : constNicks )
{
if ( mLayerGroups.contains( ql ) )
{
const auto &constLayers { mLayerGroups[ql] };
for ( const auto &ml : constLayers )
{
if ( ( ! ml->shortName().isEmpty() && ml->shortName() == queryLayer ) || ( ml->name() == queryLayer ) )
{
queryLayerName = ql;
}
if ( ml->flags().testFlag( QgsMapLayer::Identifiable ) )
{
hasGroupAndQueryable = true;
break;
}
}
break;
}
}
}
// Only throw if it's not a group or the group has no queryable children
if ( ! hasGroupAndQueryable )
{
const QString msg { QObject::tr( "The layer '%1' is not queryable." ).arg( queryLayerName ) };
throw QgsBadRequestException( QStringLiteral( "LayerNotQueryable" ), msg );
}
}
}

Expand Down Expand Up @@ -3138,5 +3172,32 @@ namespace QgsWms
{
std::unique_ptr<QImage> tmpImage( createImage( 1, 1, false ) );
return tmpImage->dotsPerMeterX() / 1000.0;
}

QStringList QgsRenderer::flattenedQueryLayers() const
{
QStringList result;
std::function <QStringList( const QString &name )> findLeaves = [ & ]( const QString & name ) -> QStringList
{
QStringList _result;
if ( mLayerGroups.contains( name ) )
{
for ( const auto &l : mLayerGroups[ name ] )
{
_result.append( findLeaves( l->shortName().isEmpty() ? l->name() : l->shortName() ) );
}
}
else
{
_result.append( name );
}
return _result;
};
for ( const auto &name : mWmsParameters.queryLayersNickname() )
{
result.append( findLeaves( name ) );
}
return result;
}

} // namespace QgsWms
3 changes: 2 additions & 1 deletion src/server/services/wms/qgswmsrenderer.h
Expand Up @@ -306,7 +306,8 @@ namespace QgsWms
QStringList mRestrictedLayers;
QMap<QString, QgsMapLayer *> mNicknameLayers;
QMap<QString, QList<QgsMapLayer *> > mLayerGroups;
QList<QgsMapLayer *> mTemporaryLayers;
QList<QgsMapLayer *> mTemporaryLayers;
QStringList flattenedQueryLayers() const;

public:

Expand Down
111 changes: 111 additions & 0 deletions tests/src/python/test_qgsserver_wms_getfeatureinfo.py
Expand Up @@ -516,6 +516,117 @@ def testGetFeatureInfoPostgresTypes(self):
attribute.get('value')), {
'c': 4.0, 'd': 5.0})

def testGetFeatureInfoGroupedLayers(self):
"""Test that we can get feature info from the top and group layers"""

# areas+and+symbols (not nested)
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=areas+and+symbols' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_areas',
'test_project_wms_grouped_layers.qgs')

# areas+and+symbols (nested)
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=areas+and+symbols' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_areas_nested',
'test_project_wms_grouped_nested_layers.qgs')

# as-areas-short-name
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=as-areas-short-name' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_areas_nested_shortname',
'test_project_wms_grouped_nested_layers.qgs')

# Top level: QGIS Server - Grouped Layer
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=QGIS+Server+-+Grouped Nested Layer' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_top',
'test_project_wms_grouped_nested_layers.qgs')

# Multiple matches from 2 layer groups
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=areas+and+symbols,city+and+district+boundaries' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_areas_cities',
'test_project_wms_grouped_nested_layers.qgs')

# no_query group (nested)
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=no_query' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_no_query',
'test_project_wms_grouped_nested_layers.qgs')

# query_child group (nested)
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=query_child' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_query_child',
'test_project_wms_grouped_nested_layers.qgs')

# child_ok group (nested)
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=child_ok' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_query_child',
'test_project_wms_grouped_nested_layers.qgs')

# as_areas_query_copy == as-areas-short-name-query-copy (nested)
self.wms_request_compare('GetFeatureInfo',
'&BBOX=52.44095517977704901,10.71171069440170776,52.440955186258563,10.71171070552261817' +
'&CRS=EPSG:4326' +
'&WIDTH=2&HEIGHT=2' +
'&QUERY_LAYERS=as-areas-short-name-query-copy' +
'&INFO_FORMAT=text/plain' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_query_child',
'test_project_wms_grouped_nested_layers.qgs')


if __name__ == '__main__':
unittest.main()
Binary file modified tests/testdata/qgis_server/db.gpkg
Binary file not shown.
63 changes: 61 additions & 2 deletions tests/testdata/qgis_server/getprojectsettings.txt
Expand Up @@ -198,7 +198,66 @@ Content-Type: text/xml; charset=utf-8
<Attribute precision="0" type="QString" editType="TextEdit" typeName="String" name="utf8nameè" comment="" length="13"/>
</Attributes>
</Layer>
<Layer mutuallyExclusive="0" visible="1">
<Layer geometryType="Point" queryable="1" displayField="alias_name" visible="1">
<Name>fields_alias</Name>
<Title>A test vector layer</Title>
<Abstract>A test vector layer with unicode òà</Abstract>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Style>
<Name>default</Name>
<Title>default</Title>
<LegendURL>
<Format>image/png</Format>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?*****" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</LegendURL>
</Style>
<TreeName>fields_alias</TreeName>
<Attributes>
<Attribute precision="0" type="qlonglong" editType="TextEdit" typeName="Integer64" alias="alias_id" name="id" comment="" length="10"/>
<Attribute precision="0" type="QString" editType="TextEdit" typeName="String" alias="alias_name" name="name" comment="" length="10"/>
<Attribute precision="0" type="QString" editType="TextEdit" typeName="String" name="utf8nameè" comment="" length="13"/>
</Attributes>
</Layer>
<Layer geometryType="Point" queryable="1" displayField="name" visible="1">
<Name>exclude_attribute</Name>
<Title>A test vector layer</Title>
<Abstract>A test vector layer with unicode òà</Abstract>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Style>
<Name>default</Name>
<Title>default</Title>
<LegendURL>
<Format>image/png</Format>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?*****" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</LegendURL>
</Style>
<TreeName>exclude_attribute</TreeName>
<Attributes>
<Attribute precision="0" type="qlonglong" editType="TextEdit" typeName="Integer64" name="id" comment="" length="10"/>
<Attribute precision="0" type="QString" editType="TextEdit" typeName="String" name="utf8nameè" comment="" length="13"/>
</Attributes>
</Layer>
<Layer mutuallyExclusive="0" visible="1" queryable="1">
<Name>group_name</Name>
<Title>Group title</Title>
<Abstract>Group abstract</Abstract>
Expand Down Expand Up @@ -244,7 +303,7 @@ Content-Type: text/xml; charset=utf-8
</Attributes>
</Layer>
</Layer>
<Layer mutuallyExclusive="0" visible="1">
<Layer mutuallyExclusive="0" queryable="0" visible="1">
<Name>groupwithoutshortname</Name>
<Title>groupwithoutshortname</Title>
<CRS>CRS:84</CRS>
Expand Down

0 comments on commit d87bd22

Please sign in to comment.