Skip to content
Permalink
Browse files

[server] Allow WMS GetFeatureInfo on root layer and groups

If the group (or the root) has any queryable child, it is flagged as queryable

Also fixes an issue with json enconding with rasters.

Fixes #21697

Funded by: Kanton Zug
  • Loading branch information
elpaso committed Apr 4, 2019
1 parent 7e23f4d commit 9a689aff555a5b5ae0a28e4dee452ab1ce95de25
@@ -841,6 +841,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 );
@@ -862,7 +871,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 )
@@ -929,6 +938,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 );
@@ -1857,6 +1876,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

@@ -82,6 +82,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
@@ -159,6 +159,32 @@ qreal QgsWmsRenderContext::dotsPerMm() const
return dpm / 1000.0;
}

QStringList QgsWmsRenderContext::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 : mParameters.queryLayersNickname() )
{
result.append( findLeaves( name ) );
}
return result;
}

QList<QgsMapLayer *> QgsWmsRenderContext::layersToRender() const
{
return mLayersToRender;
@@ -340,7 +366,7 @@ void QgsWmsRenderContext::searchLayersToRender()

if ( mFlags & AddQueryLayers )
{
for ( const QString &layer : mParameters.queryLayersNickname() )
for ( const QString &layer : flattenedQueryLayers() )
{
if ( mNicknameLayers.contains( layer )
&& !mLayersToRender.contains( mNicknameLayers[layer] ) )
@@ -471,6 +497,11 @@ bool QgsWmsRenderContext::layerScaleVisibility( const QString &name ) const
return visible;
}

QMap<QString, QList<QgsMapLayer *> > QgsWmsRenderContext::layerGroups() const
{
return mLayerGroups;
}

void QgsWmsRenderContext::removeUnwantedLayers()
{
QList<QgsMapLayer *> layers;
@@ -179,6 +179,12 @@ namespace QgsWms
*/
qreal dotsPerMm() const;

/**
* Return a list of query layer names where group names are replaced by the names of their layer components.
* \since QGIS 3.8
*/
QStringList flattenedQueryLayers() const;

#ifdef HAVE_SERVER_PYTHON_PLUGINS

/**
@@ -187,6 +193,12 @@ namespace QgsWms
QgsAccessControl *accessControl() const;
#endif

/**
* Returns a map having layer group names as keys and a list of layer instances as values.
* \since QGIS 3.8
*/
QMap<QString, QList<QgsMapLayer *> > layerGroups() const;

private:
void initNicknameLayers();
void initRestrictedLayers();
@@ -1135,7 +1135,7 @@ 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 = mContext.flattenedQueryLayers( );

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

@@ -1322,8 +1322,39 @@ namespace QgsWms
{
QgsWmsParameter param( QgsWmsParameter::LAYER );
param.mValue = queryLayer;
throw QgsBadRequestException( QgsServiceException::OGC_LAYER_NOT_QUERYABLE,
param );
// Check if this layer belongs to a group and the group has any queryable layers
bool hasGroupAndQueryable { false };
if ( ! mContext.parameters().queryLayersNickname().contains( queryLayer ) )
{
// Find which group this layer belongs to
const auto &constNicks { mContext.parameters().queryLayersNickname() };
for ( const auto &ql : constNicks )
{
if ( mContext.layerGroups().contains( ql ) )
{
const auto &constLayers { mContext.layerGroups()[ql] };
for ( const auto &ml : constLayers )
{
if ( ( ! ml->shortName().isEmpty() && ml->shortName() == queryLayer ) || ( ml->name() == queryLayer ) )
{
param.mValue = 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 )
{
throw QgsBadRequestException( QgsServiceException::OGC_LAYER_NOT_QUERYABLE,
param );
}
}
}

@@ -2206,7 +2237,7 @@ namespace QgsWms
exporter.setAttributes( attributes );
exporter.setIncludeGeometry( withGeometry );

for ( const auto feature : features )
for ( const auto &feature : qgis::as_const( features ) )
{
if ( json.right( 1 ).compare( QStringLiteral( "}" ) ) == 0 )
{
@@ -2219,6 +2250,10 @@ namespace QgsWms
}
else // raster layer
{
if ( json.right( 1 ).compare( QStringLiteral( "}" ) ) == 0 )
{
json.append( QStringLiteral( "," ) );
}
json.append( QStringLiteral( "{" ) );
json.append( QStringLiteral( "\"type\":\"Feature\",\n" ) );
json.append( QStringLiteral( "\"id\":\"%1\",\n" ).arg( layer->name() ) );
@@ -591,6 +591,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=application/json' +
'&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=application/json' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_areas',
'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=application/json' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_name_areas',
'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=application/json' +
'&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=application/json' +
'&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=application/json' +
'&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=application/json' +
'&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=application/json' +
'&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=application/json' +
'&I=0&J=1' +
'&FEATURE_COUNT=10',
'wms_getfeatureinfo_group_query_child',
'test_project_wms_grouped_nested_layers.qgs')


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

0 comments on commit 9a689af

Please sign in to comment.
You can’t perform that action at this time.