Skip to content

Commit

Permalink
Merge pull request #50281 from rouault/fix_29391
Browse files Browse the repository at this point in the history
[WFS provider] Recognize OGC HTTP URIs
  • Loading branch information
rouault committed Sep 28, 2022
2 parents aaa187d + 2b5b5ed commit cb1e62a
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 55 deletions.
41 changes: 18 additions & 23 deletions src/core/proj/qgscoordinatereferencesystem.cpp
Expand Up @@ -40,6 +40,7 @@
#include "qgssettings.h"
#include "qgsogrutils.h"
#include "qgsdatums.h"
#include "qgsogcutils.h"
#include "qgsprojectionfactors.h"
#include "qgsprojoperation.h"

Expand Down Expand Up @@ -402,33 +403,27 @@ bool QgsCoordinateReferenceSystem::createFromOgcWmsCrs( const QString &crs )

QString wmsCrs = crs;

thread_local const QRegularExpression re_uri( QRegularExpression::anchoredPattern( QStringLiteral( "http://www\\.opengis\\.net/def/crs/([^/]+).+/([^/]+)" ) ), QRegularExpression::CaseInsensitiveOption );
QRegularExpressionMatch match = re_uri.match( wmsCrs );
if ( match.hasMatch() )
QString authority;
QString code;
const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crs, authority, code );
const QString authorityLower = authority.toLower();
if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::AUTH_CODE &&
( authorityLower == QLatin1String( "user" ) ||
authorityLower == QLatin1String( "custom" ) ||
authorityLower == QLatin1String( "qgis" ) ) )
{
wmsCrs = match.captured( 1 ) + ':' + match.captured( 2 );
}
else
{
thread_local const QRegularExpression re_urn( QRegularExpression::anchoredPattern( QStringLiteral( "urn:ogc:def:crs:([^:]+).+(?<=:)([^:]+)" ) ), QRegularExpression::CaseInsensitiveOption );
match = re_urn.match( wmsCrs );
if ( match.hasMatch() )
{
wmsCrs = match.captured( 1 ) + ':' + match.captured( 2 );
}
else
if ( createFromSrsId( code.toInt() ) )
{
thread_local const QRegularExpression re_urn_custom( QRegularExpression::anchoredPattern( QStringLiteral( "(user|custom|qgis):(\\d+)" ) ), QRegularExpression::CaseInsensitiveOption );
match = re_urn_custom.match( wmsCrs );
if ( match.hasMatch() && createFromSrsId( match.captured( 2 ).toInt() ) )
{
locker.changeMode( QgsReadWriteLocker::Write );
if ( !sDisableOgcCache )
sOgcCache()->insert( crs, *this );
return d->mIsValid;
}
locker.changeMode( QgsReadWriteLocker::Write );
if ( !sDisableOgcCache )
sOgcCache()->insert( crs, *this );
return d->mIsValid;
}
}
else if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
{
wmsCrs = authority + ':' + code;
}

// first chance for proj 6 - scan through legacy systems and try to use authid directly
const QString legacyKey = wmsCrs.toLower();
Expand Down
35 changes: 12 additions & 23 deletions src/core/qgsgml.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsmessagelog.h"
#include "qgsnetworkaccessmanager.h"
#include "qgswkbptr.h"
#include "qgsogcutils.h"
#include "qgsogrutils.h"
#include "qgsapplication.h"
#include <QBuffer>
Expand Down Expand Up @@ -1259,37 +1260,25 @@ int QgsGmlStreamingParser::readEpsgFromAttribute( int &epsgNr, const XML_Char **
{
if ( strcmp( attr[i], "srsName" ) == 0 )
{
const QString epsgString( attr[i + 1] );
QString epsgNrString;
bool bIsUrn = false;
if ( epsgString.startsWith( QLatin1String( "http://www.opengis.net/gml/srs/" ) ) ) //e.g. geoserver: "http://www.opengis.net/gml/srs/epsg.xml#4326"
const QString srsName( attr[i + 1] );
QString authority;
QString code;
const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( srsName, authority, code );
if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
{
epsgNrString = epsgString.section( '#', 1, 1 );
}
// WFS >= 1.1
else if ( epsgString.startsWith( QLatin1String( "urn:ogc:def:crs:EPSG:" ) ) ||
epsgString.startsWith( QLatin1String( "urn:x-ogc:def:crs:EPSG:" ) ) )
{
bIsUrn = true;
epsgNrString = epsgString.split( ':' ).last();
}
else if ( epsgString.startsWith( QLatin1String( "http://www.opengis.net/def/crs/EPSG/" ) ) ) //e.g. geoserver: "http://www.opengis.net/def/crs/EPSG/4326"
{
bIsUrn = true;
epsgNrString = epsgString.split( '/' ).last();
}
else //e.g. umn mapserver: "EPSG:4326">
{
epsgNrString = epsgString.section( ':', 1, 1 );
return 1;
}
const bool bIsUrn = ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_URN ||
crsFlavor == QgsOgcCrsUtils::CRSFlavor::X_OGC_URN ||
crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_HTTP_URI );
bool conversionOk;
const int eNr = epsgNrString.toInt( &conversionOk );
const int eNr = code.toInt( &conversionOk );
if ( !conversionOk )
{
return 1;
}
epsgNr = eNr;
mSrsName = epsgString;
mSrsName = srsName;

const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( epsgNr ) );
if ( crs.isValid() )
Expand Down
45 changes: 45 additions & 0 deletions src/core/qgsogcutils.cpp
Expand Up @@ -3678,3 +3678,48 @@ QString QgsOgcUtilsExpressionFromFilter::errorMessage() const
{
return mErrorMessage;
}

QgsOgcCrsUtils::CRSFlavor QgsOgcCrsUtils::parseCrsName( const QString &crsName, QString &authority, QString &code )
{
const thread_local QRegularExpression re_url( QRegularExpression::anchoredPattern( QStringLiteral( "http://www\\.opengis\\.net/gml/srs/epsg\\.xml#(.+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re_url.match( crsName ); match.hasMatch() )
{
authority = QStringLiteral( "EPSG" );
code = match.captured( 1 );
return CRSFlavor::HTTP_EPSG_DOT_XML;
}

const thread_local QRegularExpression re_ogc_urn( QRegularExpression::anchoredPattern( QStringLiteral( "urn:ogc:def:crs:([^:]+).+(?<=:)([^:]+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re_ogc_urn.match( crsName ); match.hasMatch() )
{
authority = match.captured( 1 );
code = match.captured( 2 );
return CRSFlavor::OGC_URN;
}

const thread_local QRegularExpression re_x_ogc_urn( QRegularExpression::anchoredPattern( QStringLiteral( "urn:x-ogc:def:crs:([^:]+).+(?<=:)([^:]+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re_x_ogc_urn.match( crsName ); match.hasMatch() )
{
authority = match.captured( 1 );
code = match.captured( 2 );
return CRSFlavor::X_OGC_URN;
}

const thread_local QRegularExpression re_http_uri( QRegularExpression::anchoredPattern( QStringLiteral( "http://www\\.opengis\\.net/def/crs/([^/]+).+/([^/]+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re_http_uri.match( crsName ); match.hasMatch() )
{
authority = match.captured( 1 );
code = match.captured( 2 );
return CRSFlavor::OGC_HTTP_URI;
}

const thread_local QRegularExpression re_auth_code( QRegularExpression::anchoredPattern( QStringLiteral( "([^:]+):(.+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re_auth_code.match( crsName ); match.hasMatch() )
{
authority = match.captured( 1 );
code = match.captured( 2 );
return CRSFlavor::AUTH_CODE;
}

return CRSFlavor::UNKNOWN;
}
36 changes: 36 additions & 0 deletions src/core/qgsogcutils.h
Expand Up @@ -571,6 +571,42 @@ class QgsOgcUtilsSQLStatementToFilter
QString &srsName,
bool &axisInversion );
};

/**
* \ingroup core
* \brief Utilities related to OGC CRS encodings.
* \note not available in Python bindings
* \since QGIS 3.28
*/
class CORE_EXPORT QgsOgcCrsUtils
{
public:

//! CRS flavor
enum class CRSFlavor
{
UNKNOWN, //! unknown/unhandled flavor
AUTH_CODE, //! e.g EPSG:4326
HTTP_EPSG_DOT_XML, //! e.g. http://www.opengis.net/gml/srs/epsg.xml#4326 (called "OGC HTTP URL" in GeoServer WFS configuration panel)
OGC_URN, //! e.g. urn:ogc:def:crs:EPSG::4326
X_OGC_URN, //! e.g. urn:x-ogc:def:crs:EPSG::4326
OGC_HTTP_URI, //! e.g. http://www.opengis.net/def/crs/EPSG/0/4326
};

/**
* Parse a CRS name in one of the flavors of OGC services, and decompose it
* as authority and code.
*
* \param crsName CRS name, like "EPSG:4326", "http://www.opengis.net/gml/srs/epsg.xml#4326", "urn:ogc:def:crs:EPSG::4326", etc.
* \param[out] authority CRS authority.
* \param[out] code CRS code.
* \return CRS flavor (UNKNOWN if crsName could not been parsed.
*/
static CRSFlavor parseCrsName( const QString &crsName, QString &authority, QString &code );
};

Q_DECLARE_METATYPE( QgsOgcCrsUtils::CRSFlavor )

#endif // #ifndef SIP_RUN

#endif // QGSOGCUTILS_H
14 changes: 5 additions & 9 deletions src/providers/wfs/qgswfscapabilities.cpp
Expand Up @@ -657,16 +657,12 @@ void QgsWfsCapabilities::capabilitiesReplyFinished()

QString QgsWfsCapabilities::NormalizeSRSName( const QString &crsName )
{
const QRegularExpression re( QRegularExpression::anchoredPattern( QStringLiteral( "urn:ogc:def:crs:([^:]+).+?([^:]+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re.match( crsName ); match.hasMatch() )
QString authority;
QString code;
const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crsName, authority, code );
if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
{
return match.captured( 1 ) + ':' + match.captured( 2 );
}
// urn:x-ogc:def:crs:EPSG:xxxx as returned by http://maps.warwickshire.gov.uk/gs/ows? in WFS 1.1
const QRegularExpression re2( QRegularExpression::anchoredPattern( QStringLiteral( "urn:x-ogc:def:crs:([^:]+).+?([^:]+)" ) ), QRegularExpression::CaseInsensitiveOption );
if ( const QRegularExpressionMatch match = re2.match( crsName ); match.hasMatch() )
{
return match.captured( 1 ) + ':' + match.captured( 2 );
return authority + ':' + code;
}
return crsName;
}
Expand Down
38 changes: 38 additions & 0 deletions tests/src/core/testqgsogcutils.cpp
Expand Up @@ -75,6 +75,9 @@ class TestQgsOgcUtils : public QObject

void testSQLStatementToOgcFilter();
void testSQLStatementToOgcFilter_data();

void testParseCrsName();
void testParseCrsName_data();
};


Expand Down Expand Up @@ -1247,5 +1250,40 @@ void TestQgsOgcUtils::testSQLStatementToOgcFilter_data()
"</fes:Filter>" );
}


void TestQgsOgcUtils::testParseCrsName()
{
QFETCH( QString, crsName );
QFETCH( QgsOgcCrsUtils::CRSFlavor, expectedFlavor );
QFETCH( QString, expectedAuthority );
QFETCH( QString, expectedCode );

QString authority;
QString code;
const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crsName, authority, code );
QCOMPARE( expectedFlavor, crsFlavor );
QCOMPARE( expectedAuthority, authority );
QCOMPARE( expectedCode, code );
}

void TestQgsOgcUtils::testParseCrsName_data()
{
QTest::addColumn<QString>( "crsName" );
QTest::addColumn<QgsOgcCrsUtils::CRSFlavor>( "expectedFlavor" );
QTest::addColumn<QString>( "expectedAuthority" );
QTest::addColumn<QString>( "expectedCode" );

QTest::newRow( "unknown" ) << QStringLiteral( "foo" ) << QgsOgcCrsUtils::CRSFlavor::UNKNOWN << QString() << QString();
QTest::newRow( "unknown2" ) << QStringLiteral( "EPSG:" ) << QgsOgcCrsUtils::CRSFlavor::UNKNOWN << QString() << QString();
QTest::newRow( "AUTH_CODE" ) << QStringLiteral( "EPSG:1234" ) << QgsOgcCrsUtils::CRSFlavor::AUTH_CODE << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
QTest::newRow( "HTTP_EPSG_DOT_XML" ) << QStringLiteral( "http://www.opengis.net/gml/srs/epsg.xml#1234" ) << QgsOgcCrsUtils::CRSFlavor::HTTP_EPSG_DOT_XML << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
QTest::newRow( "OGC_URN" ) << QStringLiteral( "urn:ogc:def:crs:EPSG::1234" ) << QgsOgcCrsUtils::CRSFlavor::OGC_URN << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
QTest::newRow( "OGC_URN missing col" ) << QStringLiteral( "urn:ogc:def:crs:EPSG:1234" ) << QgsOgcCrsUtils::CRSFlavor::OGC_URN << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
QTest::newRow( "X_OGC_URN" ) << QStringLiteral( "urn:x-ogc:def:crs:EPSG::1234" ) << QgsOgcCrsUtils::CRSFlavor::X_OGC_URN << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
QTest::newRow( "X_OGC_URN missing col" ) << QStringLiteral( "urn:x-ogc:def:crs:EPSG:1234" ) << QgsOgcCrsUtils::CRSFlavor::X_OGC_URN << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
QTest::newRow( "OGC_HTTP_URI" ) << QStringLiteral( "http://www.opengis.net/def/crs/EPSG/0/1234" ) << QgsOgcCrsUtils::CRSFlavor::OGC_HTTP_URI << QStringLiteral( "EPSG" ) << QStringLiteral( "1234" );
}


QGSTEST_MAIN( TestQgsOgcUtils )
#include "testqgsogcutils.moc"

0 comments on commit cb1e62a

Please sign in to comment.