Skip to content
Permalink
Browse files

[WFS provider] Add heuristics to detect MapServer WFS 1.1 behaviour (…

…sometimes)

Fix #15061

MapServer honours EPSG axis order in WFS 1.1, but returns srsName in GetFeature
response with EPSG:XXXX syntax instead of urn EPSG srs. This confuses the GML
parser that thinks that no axis inversion should then happen.

The heuristics here consist in checking the envelope of the response with the
capabilities extent.

This should be safe and should work for layers with non global extent, but will
not detect all issues.

Proper fix is either to force WFS 1.0, or upgrade to MapServer 7.0 with WFS 2.0
  • Loading branch information
rouault committed Jun 18, 2016
1 parent eee599e commit f3549981a643aadeede0cfe5fb54b12aec7a8908
@@ -881,10 +881,15 @@ void QgsGmlStreamingParser::endElement( const XML_Char* el )
//create bounding box from mStringCash
if ( mCurrentExtent.isNull() &&
!mBoundedByNullFound &&
createBBoxFromCoordinateString( mCurrentExtent, mStringCash ) != 0 )
!createBBoxFromCoordinateString( mCurrentExtent, mStringCash ) )
{
QgsDebugMsg( "creation of bounding box failed" );
}
if ( !mCurrentExtent.isNull() && mLayerExtent.isNull() &&
mCurrentFeature == nullptr && mFeatureCount == 0 )
{
mLayerExtent = mCurrentExtent;
}

mParseModeStack.pop();
}
@@ -1198,6 +1203,7 @@ int QgsGmlStreamingParser::readEpsgFromAttribute( int& epsgNr, const XML_Char**
return 1;
}
epsgNr = eNr;
mSrsName = epsgString;

QgsCoordinateReferenceSystem crs = QgsCRSCache::instance()->crsByOgcWmsCrs( QString( "EPSG:%1" ).arg( epsgNr ) );
if ( crs.isValid() )
@@ -1230,22 +1236,22 @@ QString QgsGmlStreamingParser::readAttribute( const QString& attributeName, cons
return QString();
}

int QgsGmlStreamingParser::createBBoxFromCoordinateString( QgsRectangle &r, const QString& coordString ) const
bool QgsGmlStreamingParser::createBBoxFromCoordinateString( QgsRectangle &r, const QString& coordString ) const
{
QList<QgsPoint> points;
if ( pointsFromCoordinateString( points, coordString ) != 0 )
{
return 2;
return false;
}

if ( points.size() < 2 )
{
return 3;
return false;
}

r.set( points[0], points[1] );

return 0;
return true;
}

int QgsGmlStreamingParser::pointsFromCoordinateString( QList<QgsPoint>& points, const QString& coordString ) const
@@ -105,6 +105,12 @@ class CORE_EXPORT QgsGmlStreamingParser
/** Return the EPSG code, or 0 if unknown */
int getEPSGCode() const { return mEpsg; }

/** Return the value of the srsName attribute */
const QString& srsName() const { return mSrsName; }

/** Return layer bounding box */
const QgsRectangle& layerExtent() const { return mLayerExtent; }

/** Return the geometry type */
QGis::WkbType wkbType() const { return mWkbType; }

@@ -182,9 +188,8 @@ class CORE_EXPORT QgsGmlStreamingParser
@return attribute value or an empty string if no such attribute
*/
QString readAttribute( const QString& attributeName, const XML_Char** attr ) const;
/** Creates a rectangle from a coordinate string.
@return 0 in case of success*/
int createBBoxFromCoordinateString( QgsRectangle &bb, const QString& coordString ) const;
/** Creates a rectangle from a coordinate string. */
bool createBBoxFromCoordinateString( QgsRectangle &bb, const QString& coordString ) const;
/** Creates a set of points from a coordinate string.
@param points list that will contain the created points
@param coordString the text containing the coordinates
@@ -285,6 +290,10 @@ class CORE_EXPORT QgsGmlStreamingParser
ParseMode mCoorMode;
/** EPSG of parsed features geometries */
int mEpsg;
/** Literal srsName attribute */
QString mSrsName;
/** Layer bounding box */
QgsRectangle mLayerExtent;
/** GML namespace URI */
QString mGMLNameSpaceURI;
const char* mGMLNameSpaceURIPtr;
@@ -24,6 +24,7 @@
#include "qgswfsprovider.h"
#include "qgswfsshareddata.h"
#include "qgswfsutils.h"
#include "qgscrscache.h"

#include <QDir>
#include <QProgressDialog>
@@ -540,11 +541,34 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )

if ( featurePtrList.size() != 0 )
{
// Heuristics to try to detect MapServer WFS 1.1 that honours EPSG axis order, but returns
// EPSG:XXXX srsName and not EPSG urns
if ( pagingIter == 1 && featureCountForThisResponse == 0 &&
mShared->mWFSVersion.startsWith( "1.1" ) &&
parser->srsName().startsWith( "EPSG:" ) &&
!parser->layerExtent().isNull() &&
!mShared->mURI.ignoreAxisOrientation() &&
!mShared->mURI.invertAxisOrientation() )
{
QgsCoordinateReferenceSystem crs = QgsCRSCache::instance()->crsByOgcWmsCrs( parser->srsName() );
if ( crs.isValid() && crs.axisInverted() &&
!mShared->mCapabilityExtent.contains( parser->layerExtent() ) )
{
QgsRectangle invertedRectangle( parser->layerExtent() );
invertedRectangle.invert();
if ( mShared->mCapabilityExtent.contains( invertedRectangle ) )
{
mShared->mGetFeatureEPSGDotHonoursEPSGOrder = true;
QgsDebugMsg( "Server is likely MapServer. Using mGetFeatureEPSGDotHonoursEPSGOrder mode" );
}
}
}

QVector<QgsWFSFeatureGmlIdPair> featureList;
for ( int i = 0;i < featurePtrList.size();i++ )
{
QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair& featPair = featurePtrList[i];
const QgsFeature& f = *( featPair.first );
QgsFeature& f = *( featPair.first );
QString gmlId( featPair.second );
if ( gmlId.isEmpty() )
{
@@ -566,6 +590,12 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
disablePaging = true;
QgsDebugMsg( "Server does not seem to properly support paging since it returned the same first feature for 2 different page requests. Disabling paging" );
}

if ( mShared->mGetFeatureEPSGDotHonoursEPSGOrder && f.geometry() )
{
f.geometry()->transform( QTransform( 0, 1, 1, 0, 0, 0 ) );
}

featureList.push_back( QgsWFSFeatureGmlIdPair( f, gmlId ) );
delete featPair.first;
if (( i > 0 && ( i % 1000 ) == 0 ) || i + 1 == featurePtrList.size() )
@@ -43,6 +43,7 @@ QgsWFSSharedData::QgsWFSSharedData( const QString& uri )
, mHideProgressDialog( mURI.hideDownloadProgressDialog() )
, mDistinctSelect( false )
, mHasWarnedAboutMissingFeatureId( false )
, mGetFeatureEPSGDotHonoursEPSGOrder( false )
, mDownloader( nullptr )
, mDownloadFinished( false )
, mGenCounter( 0 )
@@ -171,6 +171,10 @@ class QgsWFSSharedData : public QObject
/** Create GML parser */
QgsGmlStreamingParser* createParser();

/** If the server (typically MapServer WFS 1.1) honours EPSG axis order, but returns
EPSG:XXXX srsName and not EPSG urns */
bool mGetFeatureEPSGDotHonoursEPSGOrder;

private:

/** Main mutex to protect most data members that can be modified concurrently */
@@ -2020,6 +2020,94 @@ def testGeomedia(self):
self.assertEqual(features[0]['intfield'], 1)
self.assertEqual(features[1]['intfield'], 2)

def testMapServerWFS1_1_EPSG_4326(self):
"""Test interoperability with MapServer WFS 1.1."""

endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_mapserver_wfs_1_1'

with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.1.0'), 'wb') as f:
f.write("""
<wfs:WFS_Capabilities version="1.1.0" xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://schemas.opengis.net/gml">
<FeatureTypeList>
<FeatureType>
<Name>my:typename</Name>
<Title>Title</Title>
<Abstract>Abstract</Abstract>
<DefaultCRS>urn:ogc:def:crs:EPSG::4326</DefaultCRS>
<ows:WGS84BoundingBox>
<ows:LowerCorner>2 49</ows:LowerCorner>
<ows:UpperCorner>2 49</ows:UpperCorner>
</ows:WGS84BoundingBox>
</FeatureType>
</FeatureTypeList>
</wfs:WFS_Capabilities>""".encode('UTF-8'))

with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.1.0&TYPENAME=my:typename'), 'wb') as f:
f.write("""
<schema
targetNamespace="http://my"
xmlns:my="http://my"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:gml="http://www.opengis.net/gml"
elementFormDefault="qualified" version="0.1" >
<import namespace="http://www.opengis.net/gml"
schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd" />
<element name="typename"
type="my:typenameType"
substitutionGroup="gml:_Feature" />
<complexType name="typenameType">
<complexContent>
<extension base="gml:AbstractFeatureType">
<sequence>
<element name="geometryProperty" type="gml:GeometryPropertyType" minOccurs="0" maxOccurs="1"/>
</sequence>
</extension>
</complexContent>
</complexType>
</schema>
""".encode('UTF-8'))

with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.1.0&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), 'wb') as f:
f.write("""
<wfs:FeatureCollection
xmlns:my="http://my"
xmlns:gml="http://www.opengis.net/gml"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:ogc="http://www.opengis.net/ogc">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>49.000000 2.000000</gml:lowerCorner>
<gml:upperCorner>49.000000 2.000000</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<gml:featureMember>
<my:typename gml:id="typename.1">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>49.000000 2.000000</gml:lowerCorner>
<gml:upperCorner>49.000000 2.000000</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<my:geometryProperty>
<gml:Point srsName="EPSG:4326">
<gml:pos>49.000000 2.000000</gml:pos>
</gml:Point>
</my:geometryProperty>
</my:typename>
</gml:featureMember>
</wfs:FeatureCollection>
""".encode('UTF-8'))

vl = QgsVectorLayer(u"url='http://" + endpoint + u"' typename='my:typename' version='1.1.0'", u'test', u'WFS')
assert vl.isValid()

got_f = [f for f in vl.getFeatures()]
got = got_f[0].geometry().geometry()
self.assertEqual((got.x(), got.y()), (2.0, 49.0))


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

0 comments on commit f354998

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