Skip to content

Commit

Permalink
[Server][WFS][Feature] Support resultType=hits from GetFeature 1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
rldhont committed Oct 12, 2017
1 parent 1e009b4 commit 0d350b8
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/server/services/wfs/qgswfsgetcapabilities.cpp
Expand Up @@ -380,7 +380,7 @@ namespace QgsWfs
getFeatureElement.appendChild( gfOutputFormatParameterElement );
// GetFeature resultType
QDomElement resultTypeParameterElement = getParameterElement( doc, QStringLiteral( "resultType" ),
QStringList() << QStringLiteral( "results" ) );
QStringList() << QStringLiteral( "results" ) << QStringLiteral( "hits" ) );
getFeatureElement.appendChild( resultTypeParameterElement );
// Add
oprationsElement.appendChild( getFeatureElement );
Expand Down
153 changes: 131 additions & 22 deletions src/server/services/wfs/qgswfsgetfeature.cpp
Expand Up @@ -68,6 +68,9 @@ namespace QgsWfs

QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params );

void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
QgsWfsParameters::Format format, int numberOfFeatures, const QStringList &typeNames );

void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
QgsWfsParameters::Format format, int prec, QgsCoordinateReferenceSystem &crs,
QgsRectangle *rect, const QStringList &typeNames );
Expand Down Expand Up @@ -329,29 +332,43 @@ namespace QgsWfs
outputCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( query.srsName );
}

const createFeatureParams cfp = { layerPrecision,
layerCrs,
attrIndexes,
layerExcludedAttributes,
typeName,
withGeom,
geometryName,
outputCrs
};

// Iterate through features
QgsFeatureIterator fit = vlayer->getFeatures( featureRequest );
while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
{
if ( iteratedFeatures == aRequest.startIndex )
startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList );

if ( iteratedFeatures >= aRequest.startIndex )
if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
{
while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
{
if ( iteratedFeatures >= aRequest.startIndex )
{
++sentFeatures;
}
++iteratedFeatures;
}
}
else
{
const createFeatureParams cfp = { layerPrecision,
layerCrs,
attrIndexes,
layerExcludedAttributes,
typeName,
withGeom,
geometryName,
outputCrs
};
while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
{
setGetFeature( response, aRequest.outputFormat, &feature, sentFeatures, cfp );
++sentFeatures;
if ( iteratedFeatures == aRequest.startIndex )
startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList );

if ( iteratedFeatures >= aRequest.startIndex )
{
setGetFeature( response, aRequest.outputFormat, &feature, sentFeatures, cfp );
++sentFeatures;
}
++iteratedFeatures;
}
++iteratedFeatures;
}
}

Expand All @@ -360,10 +377,17 @@ namespace QgsWfs
filterRestorer.reset();
#endif

// End of GetFeature
if ( iteratedFeatures <= aRequest.startIndex )
startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList );
endGetFeature( response, aRequest.outputFormat );
if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
{
hitGetFeature( request, response, project, aRequest.outputFormat, sentFeatures, typeNameList );
}
else
{
// End of GetFeature
if ( iteratedFeatures <= aRequest.startIndex )
startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList );
endGetFeature( response, aRequest.outputFormat );
}

}

Expand Down Expand Up @@ -853,6 +877,91 @@ namespace QgsWfs
namespace
{

void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
int numberOfFeatures, const QStringList &typeNames )
{
QDateTime now = QDateTime::currentDateTime();
QString fcString;

if ( format == QgsWfsParameters::Format::GeoJSON )
{
response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
fcString += QStringLiteral( " \"timeStamp\": \"%1\"\n" ).arg( now.toString( Qt::ISODate ) );
fcString += QStringLiteral( " \"numberOfFeatures\": %1\n" ).arg( QString::number( numberOfFeatures ) );
fcString += QLatin1String( "}" );
}
else
{
if ( format == QgsWfsParameters::Format::GML2 )
response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
else
response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );

//Prepare url
QString hrefString = serviceUrl( request, project );

QUrl mapUrl( hrefString );

QUrlQuery query( mapUrl );
query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
//Set version
if ( mWfsParameters.version().isEmpty() )
query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
else
query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );

query.removeAllQueryItems( QStringLiteral( "REQUEST" ) );
query.removeAllQueryItems( QStringLiteral( "FORMAT" ) );
query.removeAllQueryItems( QStringLiteral( "OUTPUTFORMAT" ) );
query.removeAllQueryItems( QStringLiteral( "BBOX" ) );
query.removeAllQueryItems( QStringLiteral( "FEATUREID" ) );
query.removeAllQueryItems( QStringLiteral( "TYPENAME" ) );
query.removeAllQueryItems( QStringLiteral( "FILTER" ) );
query.removeAllQueryItems( QStringLiteral( "EXP_FILTER" ) );
query.removeAllQueryItems( QStringLiteral( "MAXFEATURES" ) );
query.removeAllQueryItems( QStringLiteral( "STARTINDEX" ) );
query.removeAllQueryItems( QStringLiteral( "PROPERTYNAME" ) );
query.removeAllQueryItems( QStringLiteral( "_DC" ) );

query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
{
if ( format == QgsWfsParameters::Format::GML2 )
query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
else
query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
}
else
query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );

mapUrl.setQuery( query );

hrefString = mapUrl.toString();

//wfs:FeatureCollection valid
fcString = QStringLiteral( "<wfs:FeatureCollection" );
fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " http://schemas.opengis.net/wfs/1.0.0/wfs.xsd " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
fcString += "\n timeStamp=\"" + now.toString( Qt::ISODate ) + "\"";
fcString += "\n numberOfFeatures=\"" + QString::number( numberOfFeatures ) + "\"";
fcString += QLatin1String( ">\n" );
fcString += QStringLiteral( "</wfs:FeatureCollection>" );
}

response.write( fcString.toUtf8() );
response.flush();
}

void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
int prec, QgsCoordinateReferenceSystem &crs, QgsRectangle *rect, const QStringList &typeNames )
{
Expand Down
12 changes: 12 additions & 0 deletions src/server/services/wfs/qgswfsparameters.cpp
Expand Up @@ -226,6 +226,18 @@ namespace QgsWfs
return value( ParameterName::RESULTTYPE ).toString();
}

QgsWfsParameters::ResultType QgsWfsParameters::resultType() const
{
QString rtStr = resultTypeAsString();
if ( rtStr.isEmpty() )
return ResultType::RESULTS;

ResultType rt = ResultType::RESULTS;
if ( rtStr.compare( QLatin1String( "hits" ), Qt::CaseInsensitive ) == 0 )
rt = ResultType::HITS;
return rt;
}

QStringList QgsWfsParameters::propertyNames() const
{
return toStringListWithExp( ParameterName::PROPERTYNAME );
Expand Down
12 changes: 12 additions & 0 deletions src/server/services/wfs/qgswfsparameters.h
Expand Up @@ -67,6 +67,12 @@ namespace QgsWfs
XSD
};

enum ResultType
{
RESULTS,
HITS
};

struct Parameter
{
ParameterName mName;
Expand Down Expand Up @@ -134,6 +140,12 @@ namespace QgsWfs
*/
QString resultTypeAsString() const;

/** Returns resultType. If the RESULTTYPE parameter is not used, then the
* default value is RESULTS.
* \returns resultType
*/
ResultType resultType() const;

/** Returns PROPERTYNAME parameter as list.
* \returns propertyName parameter as list
*/
Expand Down
4 changes: 4 additions & 0 deletions tests/src/python/test_qgsserver_wfs.py
Expand Up @@ -84,6 +84,9 @@ def wfs_getfeature_compare(self, requestid, request):
query_string = '?MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), request)
header, body = self._execute_request(query_string)

if requestid == 'hits':
body = re.sub(b'timeStamp="\d+-\d+-\d+T\d+:\d+:\d+"', b'timeStamp="****-**-**T**:**:**"', body)

self.result_compare(
'wfs_getfeature_' + requestid + '.txt',
"request %s failed.\n Query: %s" % (
Expand All @@ -101,6 +104,7 @@ def test_getfeature(self):
tests.append(('start1_limit1', 'GetFeature&TYPENAME=testlayer&MAXFEATURES=1&STARTINDEX=1'))
tests.append(('srsname', 'GetFeature&TYPENAME=testlayer&SRSNAME=EPSG:3857'))
tests.append(('sortby', 'GetFeature&TYPENAME=testlayer&SORTBY=id D'))
tests.append(('hits', 'GetFeature&TYPENAME=testlayer&RESULTTYPE=hits'))

for id, req in tests:
self.wfs_getfeature_compare(id, req)
Expand Down
1 change: 1 addition & 0 deletions tests/testdata/qgis_server/wfs_getcapabilities.txt
Expand Up @@ -62,6 +62,7 @@ Content-Type: text/xml; charset=utf-8
</ows:Parameter>
<ows:Parameter name="resultType">
<ows:Value>results</ows:Value>
<ows:Value>hits</ows:Value>
</ows:Parameter>
</ows:Operation>
<ows:Operation name="Transaction">
Expand Down
6 changes: 6 additions & 0 deletions tests/testdata/qgis_server/wfs_getfeature_hits.txt
@@ -0,0 +1,6 @@
Content-Type: text/xml; subtype=gml/2.1.2; charset=utf-8

<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:qgs="http://www.qgis.org/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://www.qgis.org/gml ?MAP=/home/dhont/3liz_dev/QGIS/qgis_rldhont/tests/testdata/qgis_server/test_project_wfs.qgs&amp;RESULTTYPE=hits&amp;SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=testlayer&amp;OUTPUTFORMAT=XMLSCHEMA"
timeStamp="****-**-**T**:**:**"
numberOfFeatures="3">
</wfs:FeatureCollection>

0 comments on commit 0d350b8

Please sign in to comment.