312 changes: 228 additions & 84 deletions src/mapserver/qgswmsserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,21 +121,21 @@ QDomDocument QgsWMSServer::getCapabilities()

QList<QPair<QString, QString> > queryItems = mapUrl.queryItems();
QList<QPair<QString, QString> >::const_iterator queryIt = queryItems.constBegin();
for(; queryIt != queryItems.constEnd(); ++queryIt )
for ( ; queryIt != queryItems.constEnd(); ++queryIt )
{
if( queryIt->first.compare("REQUEST", Qt::CaseInsensitive ) == 0 )
if ( queryIt->first.compare( "REQUEST", Qt::CaseInsensitive ) == 0 )
{
mapUrl.removeQueryItem( queryIt->first );
}
else if( queryIt->first.compare("VERSION", Qt::CaseInsensitive ) == 0 )
else if ( queryIt->first.compare( "VERSION", Qt::CaseInsensitive ) == 0 )
{
mapUrl.removeQueryItem( queryIt->first );
}
else if( queryIt->first.compare("SERVICE", Qt::CaseInsensitive ) == 0 )
else if ( queryIt->first.compare( "SERVICE", Qt::CaseInsensitive ) == 0 )
{
mapUrl.removeQueryItem( queryIt->first );
}
else if( queryIt->first.compare("_DC", Qt::CaseInsensitive ) == 0 )
else if ( queryIt->first.compare( "_DC", Qt::CaseInsensitive ) == 0 )
{
mapUrl.removeQueryItem( queryIt->first );
}
Expand Down Expand Up @@ -410,7 +410,8 @@ QByteArray* QgsWMSServer::getPrint( const QString& formatString )
}
delete theImage;

QMap<QString, QString> originalLayerFilters = applyRequestedLayerFilters( layersList, layerIdList );
QMap<QString, QString> originalLayerFilters = applyRequestedLayerFilters( layersList );
QStringList selectedLayerIdList = applyFeatureSelections( layersList );

//GetPrint request needs a template parameter
std::map<QString, QString>::const_iterator templateIt = mParameterMap.find( "TEMPLATE" );
Expand All @@ -423,6 +424,7 @@ QByteArray* QgsWMSServer::getPrint( const QString& formatString )
if ( !c )
{
restoreLayerFilters( originalLayerFilters );
clearFeatureSelections( selectedLayerIdList );
return 0;
}

Expand Down Expand Up @@ -480,6 +482,7 @@ QByteArray* QgsWMSServer::getPrint( const QString& formatString )
{
delete c;
restoreLayerFilters( originalLayerFilters );
clearFeatureSelections( selectedLayerIdList );
return 0;
}

Expand Down Expand Up @@ -523,6 +526,7 @@ QByteArray* QgsWMSServer::getPrint( const QString& formatString )
throw QgsMapServiceException( "InvalidFormat", "Output format '" + formatString + "' is not supported in the GetPrint request" );
}
restoreLayerFilters( originalLayerFilters );
clearFeatureSelections( selectedLayerIdList );

delete c;
return ba;
Expand Down Expand Up @@ -552,9 +556,13 @@ QImage* QgsWMSServer::getMap()
QPainter thePainter( theImage );
thePainter.setRenderHint( QPainter::Antialiasing ); //make it look nicer

QMap<QString, QString> originalLayerFilters = applyRequestedLayerFilters( layersList, layerIdList );
QMap<QString, QString> originalLayerFilters = applyRequestedLayerFilters( layersList );
QStringList selectedLayerIdList = applyFeatureSelections( layersList );

mMapRenderer->render( &thePainter );

restoreLayerFilters( originalLayerFilters );
clearFeatureSelections( selectedLayerIdList );

QgsMapLayerRegistry::instance()->mapLayers().clear();
return theImage;
Expand Down Expand Up @@ -636,11 +644,10 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result )
if ( i_it == mParameterMap.end() )
{
std::map<QString, QString>::const_iterator x_it = mParameterMap.find( "X" );
if ( x_it == mParameterMap.end() )
if ( x_it != mParameterMap.end() )
{
return 5;
iString = x_it->second;
}
iString = x_it->second;
}
else
{
Expand All @@ -649,18 +656,17 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result )
i = iString.toInt( &conversionSuccess );
if ( !conversionSuccess )
{
return 6;
i = -1;
}

std::map<QString, QString>::const_iterator j_it = mParameterMap.find( "J" );
if ( j_it == mParameterMap.end() )
{
std::map<QString, QString>::const_iterator y_it = mParameterMap.find( "Y" );
if ( y_it == mParameterMap.end() )
if ( y_it != mParameterMap.end() )
{
return 7;
jString = y_it->second;
}
jString = y_it->second;
}
else
{
Expand All @@ -669,9 +675,34 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result )
j = jString.toInt( &conversionSuccess );
if ( !conversionSuccess )
{
return 8;
j = -1;
}

//Normally, I/J or X/Y are mandatory parameters.
//However, in order to make attribute only queries via the FILTER parameter, it is allowed to skip them if the FILTER parameter is there

QgsRectangle* featuresRect = 0;
QgsPoint* infoPoint = 0;
if ( i == -1 || j == -1 )
{
if ( mParameterMap.find( "FILTER" ) != mParameterMap.end() )
{
featuresRect = new QgsRectangle();
}
else
{
throw QgsMapServiceException( "ParameterMissing", "I/J parameters are required for GetFeatureInfo" );
}
}
else
{
infoPoint = new QgsPoint();
}

//get the layer registered in QgsMapLayerRegistry and apply possible filters
QStringList layerIds = layerSet( layersList, stylesList, mMapRenderer->destinationCrs() );
QMap<QString, QString> originalLayerFilters = applyRequestedLayerFilters( layersList );

QDomElement getFeatureInfoElement = result.createElement( "GetFeatureInfoResponse" );
result.appendChild( getFeatureInfoElement );

Expand All @@ -681,72 +712,86 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result )

QList<QgsMapLayer*> layerList;
QgsMapLayer* currentLayer = 0;
QgsPoint infoPoint; //info point in coordinates of the layer
QStringList::const_iterator layerIt;
for ( layerIt = queryLayerList.constBegin(); layerIt != queryLayerList.constEnd(); ++layerIt )
{
//create maplayers from sld parser (several layers are possible in case of feature info on a group)
layerList = mConfigParser->mapLayerFromStyle( *layerIt, "" );
QList<QgsMapLayer*>::iterator layerListIt = layerList.begin();
for(; layerListIt != layerList.end(); ++layerListIt )
for ( ; layerListIt != layerList.end(); ++layerListIt )
{
currentLayer = *layerListIt;
if ( !currentLayer || nonIdentifiableLayers.contains( currentLayer->id() ) )
{
currentLayer = *layerListIt;
if ( !currentLayer || nonIdentifiableLayers.contains( currentLayer->id() ) )
{
continue;
}
if ( infoPointToLayerCoordinates( i, j, infoPoint, mMapRenderer, currentLayer ) != 0 )
{
}

if ( infoPoint && infoPointToLayerCoordinates( i, j, infoPoint, mMapRenderer, currentLayer ) != 0 )
{
continue;
}

QDomElement layerElement = result.createElement( "Layer" );
layerElement.setAttribute( "name", currentLayer->name() );
getFeatureInfoElement.appendChild( layerElement );

//switch depending on vector or raster
QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( currentLayer );
if ( vectorLayer )
{
//is there alias info for this vector layer?
QMap< int, QString > layerAliasInfo;
QMap< QString, QMap< int, QString > >::const_iterator aliasIt = aliasInfo.find( currentLayer->id() );
if ( aliasIt != aliasInfo.constEnd() )
{
layerAliasInfo = aliasIt.value();
}
QgsDebugMsg( "Info point in layer crs: " + QString::number( infoPoint.x() ) + "//" + QString::number( infoPoint.y() ) );

QDomElement layerElement = result.createElement( "Layer" );
layerElement.setAttribute( "name", currentLayer->name() );
getFeatureInfoElement.appendChild( layerElement );
//hidden attributes for this layer
QSet<QString> layerHiddenAttributes;
QMap< QString, QSet<QString> >::const_iterator hiddenIt = hiddenAttributes.find( currentLayer->id() );
if ( hiddenIt != hiddenAttributes.constEnd() )
{
layerHiddenAttributes = hiddenIt.value();
}

//switch depending on vector or raster
QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( currentLayer );
if ( vectorLayer )
if ( featureInfoFromVectorLayer( vectorLayer, infoPoint, featureCount, result, layerElement, mMapRenderer, layerAliasInfo, layerHiddenAttributes, featuresRect ) != 0 )
{
//is there alias info for this vector layer?
QMap< int, QString > layerAliasInfo;
QMap< QString, QMap< int, QString > >::const_iterator aliasIt = aliasInfo.find( currentLayer->id() );
if ( aliasIt != aliasInfo.constEnd() )
{
layerAliasInfo = aliasIt.value();
}

//hidden attributes for this layer
QSet<QString> layerHiddenAttributes;
QMap< QString, QSet<QString> >::const_iterator hiddenIt = hiddenAttributes.find( currentLayer->id() );
if ( hiddenIt != hiddenAttributes.constEnd() )
{
layerHiddenAttributes = hiddenIt.value();
}

if ( featureInfoFromVectorLayer( vectorLayer, infoPoint, featureCount, result, layerElement, mMapRenderer, layerAliasInfo, layerHiddenAttributes ) != 0 )
{
continue;
}
continue;
}
else //raster layer
}
else //raster layer
{
QgsRasterLayer* rasterLayer = dynamic_cast<QgsRasterLayer*>( currentLayer );
if ( rasterLayer )
{
QgsRasterLayer* rasterLayer = dynamic_cast<QgsRasterLayer*>( currentLayer );
if ( rasterLayer )
{
if ( featureInfoFromRasterLayer( rasterLayer, infoPoint, result, layerElement ) != 0 )
{
continue;
}
}
else
{
if ( featureInfoFromRasterLayer( rasterLayer, infoPoint, result, layerElement ) != 0 )
{
continue;
}
}
}
else
{
continue;
}
}
}
}

if ( featuresRect )
{
QDomElement bBoxElem = result.createElement( "BoundingBox" );
bBoxElem.setAttribute( "CRS", mMapRenderer->destinationCrs().authid() );
bBoxElem.setAttribute( "minx", featuresRect->xMinimum() );
bBoxElem.setAttribute( "maxx", featuresRect->xMaximum() );
bBoxElem.setAttribute( "miny", featuresRect->yMinimum() );
bBoxElem.setAttribute( "maxy", featuresRect->yMaximum() );
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
}

restoreLayerFilters( originalLayerFilters );
delete featuresRect;
delete infoPoint;
return 0;
}

Expand Down Expand Up @@ -1101,29 +1146,29 @@ int QgsWMSServer::initializeSLDParser( QStringList& layersList, QStringList& sty
return 0;
}

int QgsWMSServer::infoPointToLayerCoordinates( int i, int j, QgsPoint& layerCoords, QgsMapRenderer* mapRender,
int QgsWMSServer::infoPointToLayerCoordinates( int i, int j, QgsPoint* layerCoords, QgsMapRenderer* mapRender,
QgsMapLayer* layer ) const
{
if ( !mapRender || !layer || !mapRender->coordinateTransform() )
if ( !layerCoords || !mapRender || !layer || !mapRender->coordinateTransform() )
{
return 1;
}

//first transform i,j to map output coordinates
QgsPoint mapPoint = mapRender->coordinateTransform()->toMapCoordinates( i, j );
//and then to layer coordinates
layerCoords = mapRender->mapToLayerCoordinates( layer, mapPoint );
*layerCoords = mapRender->mapToLayerCoordinates( layer, mapPoint );
return 0;
}

int QgsWMSServer::featureInfoFromVectorLayer( QgsVectorLayer* layer,
const QgsPoint& infoPoint,
const QgsPoint* infoPoint,
int nFeatures,
QDomDocument& infoDocument,
QDomElement& layerElement,
QgsMapRenderer* mapRender,
QMap<int, QString>& aliasMap,
QSet<QString>& hiddenAttributes ) const
QSet<QString>& hiddenAttributes, QgsRectangle* featureBBox ) const
{
if ( !layer || !mapRender )
{
Expand All @@ -1133,9 +1178,16 @@ int QgsWMSServer::featureInfoFromVectorLayer( QgsVectorLayer* layer,
//we need a selection rect (0.01 of map width)
QgsRectangle mapRect = mapRender->extent();
QgsRectangle layerRect = mapRender->mapToLayerCoordinates( layer, mapRect );
double searchRadius = ( layerRect.xMaximum() - layerRect.xMinimum() ) / 200;
QgsRectangle searchRect( infoPoint.x() - searchRadius, infoPoint.y() - searchRadius,
infoPoint.x() + searchRadius, infoPoint.y() + searchRadius );

QgsRectangle searchRect;

//info point could be 0 in case there is only an attribute filter
if ( infoPoint )
{
double searchRadius = ( layerRect.xMaximum() - layerRect.xMinimum() ) / 200;
searchRect.set( infoPoint->x() - searchRadius, infoPoint->y() - searchRadius,
infoPoint->x() + searchRadius, infoPoint->y() + searchRadius );
}

//do a select with searchRect and go through all the features
QgsVectorDataProvider* provider = layer->dataProvider();
Expand All @@ -1150,7 +1202,7 @@ int QgsWMSServer::featureInfoFromVectorLayer( QgsVectorLayer* layer,
const QgsFieldMap& fields = provider->fields();
bool addWktGeometry = mConfigParser && mConfigParser->featureInfoWithWktGeometry();

provider->select( provider->attributeIndexes(), searchRect, addWktGeometry, true );
provider->select( provider->attributeIndexes(), searchRect, addWktGeometry || featureBBox, true );
while ( provider->nextFeature( feature ) )
{
if ( featureCounter > nFeatures )
Expand Down Expand Up @@ -1188,16 +1240,25 @@ int QgsWMSServer::featureInfoFromVectorLayer( QgsVectorLayer* layer,
}

//also append the wkt geometry as an attribute
if ( addWktGeometry )
QgsGeometry* geom = feature.geometry();
if ( addWktGeometry && geom )
{
QgsGeometry* geom = feature.geometry();
if ( geom )
QDomElement geometryElement = infoDocument.createElement( "Attribute" );
geometryElement.setAttribute( "name", "geometry" );
geometryElement.setAttribute( "value", geom->exportToWkt() );
geometryElement.setAttribute( "type", "derived" );
featureElement.appendChild( geometryElement );
}
if ( featureBBox && geom && mapRender ) //extend feature info bounding box if requested
{
QgsRectangle box = mapRender->layerExtentToOutputExtent( layer, geom->boundingBox() );
if ( featureBBox->isEmpty() )
{
*featureBBox = box;
}
else
{
QDomElement geometryElement = infoDocument.createElement( "Attribute" );
geometryElement.setAttribute( "name", "geometry" );
geometryElement.setAttribute( "value", geom->exportToWkt() );
geometryElement.setAttribute( "type", "derived" );
featureElement.appendChild( geometryElement );
featureBBox->combineExtentWith( &box );
}
}
}
Expand All @@ -1207,17 +1268,17 @@ int QgsWMSServer::featureInfoFromVectorLayer( QgsVectorLayer* layer,
}

int QgsWMSServer::featureInfoFromRasterLayer( QgsRasterLayer* layer,
const QgsPoint& infoPoint,
const QgsPoint* infoPoint,
QDomDocument& infoDocument,
QDomElement& layerElement ) const
{
if ( !layer )
if ( !infoPoint || !layer )
{
return 1;
}

QMap<QString, QString> attributes;
layer->identify( infoPoint, attributes );
layer->identify( *infoPoint, attributes );

for ( QMap<QString, QString>::const_iterator it = attributes.constBegin(); it != attributes.constEnd(); ++it )
{
Expand Down Expand Up @@ -1564,11 +1625,11 @@ void QgsWMSServer::drawRasterSymbol( QgsComposerLegendItem* item, QPainter* p, d
p->drawRect( QRectF( boxSpace, currentY + yDownShift, symbolWidth, symbolHeight ) );
}

QMap<QString, QString> QgsWMSServer::applyRequestedLayerFilters( const QStringList& layerList, const QStringList& layerIds ) const
QMap<QString, QString> QgsWMSServer::applyRequestedLayerFilters( const QStringList& layerList ) const
{
QMap<QString, QString> filterMap;

if ( layerList.isEmpty() || layerIds.isEmpty() )
if ( layerList.isEmpty() )
{
return filterMap;
}
Expand Down Expand Up @@ -1765,3 +1826,86 @@ bool QgsWMSServer::testFilterStringSafety( const QString& filter ) const

return true;
}

QStringList QgsWMSServer::applyFeatureSelections( const QStringList& layerList ) const
{
QStringList layersWithSelections;
if ( layerList.isEmpty() )
{
return layersWithSelections;
}

std::map<QString, QString>::const_iterator selectionIt = mParameterMap.find( "SELECTION" );
if ( selectionIt == mParameterMap.end() )
{
return layersWithSelections;
}

QString selectionString = selectionIt->second;
QStringList selectionLayerList = selectionString.split( ";" );
QStringList::const_iterator selectionLayerIt = selectionLayerList.constBegin();
for ( ; selectionLayerIt != selectionLayerList.constEnd(); ++selectionLayerIt )
{
//separate layer name from id list
QStringList layerIdSplit = selectionLayerIt->split( ":" );
if ( layerIdSplit.size() < 2 )
{
continue;
}

//find layerId for layer name
QString layerName = layerIdSplit.at( 0 );
QgsVectorLayer* vLayer = 0;

QMap<QString, QgsMapLayer*>& layerMap = QgsMapLayerRegistry::instance()->mapLayers();
QMap<QString, QgsMapLayer*>::iterator layerIt = layerMap.begin();
for ( ; layerIt != layerMap.end(); ++layerIt )
{
if ( layerIt.value() && layerIt.value()->name() == layerName )
{
vLayer = qobject_cast<QgsVectorLayer*>( layerIt.value() );
layersWithSelections.push_back( vLayer->id() );
break;
}
}

if ( !vLayer )
{
continue;
}

QStringList idList = layerIdSplit.at( 1 ).split( "," );
QgsFeatureIds selectedIds;

QStringList::const_iterator idIt = idList.constBegin();
for ( ; idIt != idList.constEnd(); ++idIt )
{
selectedIds.insert( STRING_TO_FID( *idIt ) );
}

vLayer->setSelectedFeatures( selectedIds );
}


return layersWithSelections;
}

void QgsWMSServer::clearFeatureSelections( const QStringList& layerIds ) const
{
QMap<QString, QgsMapLayer*>& layerMap = QgsMapLayerRegistry::instance()->mapLayers();

QStringList::const_iterator layerIdIt = layerIds.constBegin();
for ( ; layerIdIt != layerIds.constEnd(); ++layerIdIt )
{
QMap<QString, QgsMapLayer*>::iterator layerIt = layerMap.find( *layerIdIt );
if ( layerIt != layerMap.end() )
{
QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( layerIt.value() );
if ( vlayer )
{
vlayer->setSelectedFeatures( QgsFeatureIds() );
}
}
}
return;
}
22 changes: 15 additions & 7 deletions src/mapserver/qgswmsserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ class QgsCoordinateReferenceSystem;
class QgsComposerLayerItem;
class QgsComposerLegendItem;
class QgsComposition;
class QgsConfigParser;
class QgsMapLayer;
class QgsMapRenderer;
class QgsPoint;
class QgsRasterLayer;
class QgsConfigParser;
class QgsRectangle;
class QgsVectorLayer;
class QgsSymbol;
class QFile;
Expand Down Expand Up @@ -106,14 +107,15 @@ class QgsWMSServer
@param j pixel y-coordinate
@param layerCoords calculated layer coordinates are assigned to this point
@return 0 in case of success*/
int infoPointToLayerCoordinates( int i, int j, QgsPoint& layerCoords, QgsMapRenderer* mapRender,
int infoPointToLayerCoordinates( int i, int j, QgsPoint* layerCoords, QgsMapRenderer* mapRender,
QgsMapLayer* layer ) const;
/**Appends feature info xml for the layer to the layer element of the feature info dom document
@return 0 in case of success*/
int featureInfoFromVectorLayer( QgsVectorLayer* layer, const QgsPoint& infoPoint, int nFeatures, QDomDocument& infoDocument, QDomElement& layerElement, QgsMapRenderer* mapRender,
QMap<int, QString>& aliasMap, QSet<QString>& hiddenAttributes ) const;
@param featureBBox the bounding box of the selected features in output CRS
@return 0 in case of success*/
int featureInfoFromVectorLayer( QgsVectorLayer* layer, const QgsPoint* infoPoint, int nFeatures, QDomDocument& infoDocument, QDomElement& layerElement, QgsMapRenderer* mapRender,
QMap<int, QString>& aliasMap, QSet<QString>& hiddenAttributes, QgsRectangle* featureBBox = 0 ) const;
/**Appends feature info xml for the layer to the layer element of the dom document*/
int featureInfoFromRasterLayer( QgsRasterLayer* layer, const QgsPoint& infoPoint, QDomDocument& infoDocument, QDomElement& layerElement ) const;
int featureInfoFromRasterLayer( QgsRasterLayer* layer, const QgsPoint* infoPoint, QDomDocument& infoDocument, QDomElement& layerElement ) const;

/**Creates a layer set and returns a stringlist with layer ids that can be passed to a QgsMapRenderer. Usually used in conjunction with readLayersAndStyles*/
QStringList layerSet( const QStringList& layersList, const QStringList& stylesList, const QgsCoordinateReferenceSystem& destCRS ) const;
Expand All @@ -140,13 +142,19 @@ class QgsWMSServer

/**Apply filter (subset) strings from the request to the layers. Example: '&FILTER=<layer1>:"AND property > 100",<layer2>:"AND bla = 'hallo!'" '
@return a map with the original filters ( layer id / filter string )*/
QMap<QString, QString> applyRequestedLayerFilters( const QStringList& layerList, const QStringList& layerIds ) const;
QMap<QString, QString> applyRequestedLayerFilters( const QStringList& layerList ) const;
/**Restores the original layer filters*/
void restoreLayerFilters( const QMap < QString, QString >& filterMap ) const;
/**Tests if a filter sql string is allowed (safe)
@return true in case of success, false if string seems unsafe*/
bool testFilterStringSafety( const QString& filter ) const;

/**Select vector features with ids specified in parameter SELECTED, e.g. ...&SELECTED=layer1:1,2,9;layer2:3,5,10&...
@return list with layer ids where selections have been created*/
QStringList applyFeatureSelections( const QStringList& layerList ) const;
/**Clear all feature selections in the given layers*/
void clearFeatureSelections( const QStringList& layerIds ) const;

/**Map containing the WMS parameters*/
std::map<QString, QString> mParameterMap;
QgsConfigParser* mConfigParser;
Expand Down