Skip to content

Commit d2e2110

Browse files
authored
Merge pull request #3812 from mhugent/wms_1_3_compliance
Wms 1 3 compliance
2 parents 3bd8a29 + 898ca57 commit d2e2110

7 files changed

+129
-26
lines changed

src/server/qgsconfigparserutils.cpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ void QgsConfigParserUtils::appendCrsElementsToLayer( QDomElement& layerElement,
6565
appendCrsElementToLayer( layerElement, CRSPrecedingElement, crs, doc );
6666
}
6767
}
68+
69+
//Support for CRS:84 is mandatory (equals EPSG:4326 with reversed axis)
70+
appendCrsElementToLayer( layerElement, CRSPrecedingElement, QString( "CRS:84" ), doc );
6871
}
6972

7073
void QgsConfigParserUtils::appendCrsElementToLayer( QDomElement& layerElement, const QDomElement& precedingElement,
@@ -77,14 +80,21 @@ void QgsConfigParserUtils::appendCrsElementToLayer( QDomElement& layerElement, c
7780
layerElement.insertAfter( crsElement, precedingElement );
7881
}
7982

80-
void QgsConfigParserUtils::appendLayerBoundingBoxes( QDomElement& layerElem, QDomDocument& doc, const QgsRectangle& layerExtent,
83+
void QgsConfigParserUtils::appendLayerBoundingBoxes( QDomElement& layerElem, QDomDocument& doc, const QgsRectangle& lExtent,
8184
const QgsCoordinateReferenceSystem& layerCRS, const QStringList &crsList, const QStringList& constrainedCrsList )
8285
{
8386
if ( layerElem.isNull() )
8487
{
8588
return;
8689
}
8790

91+
QgsRectangle layerExtent = lExtent;
92+
if ( qgsDoubleNear( layerExtent.xMinimum(), layerExtent.xMaximum() ) || qgsDoubleNear( layerExtent.yMinimum(), layerExtent.yMaximum() ) )
93+
{
94+
//layer bbox cannot be empty
95+
layerExtent.grow( 0.000001 );
96+
}
97+
8898
QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( GEO_EPSG_CRS_AUTHID );
8999

90100
QString version = doc.documentElement().attribute( QStringLiteral( "version" ) );

src/server/qgsserverprojectparser.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ void QgsServerProjectParser::serviceCapabilities( QDomElement& parentElement, QD
586586
//Fees
587587
QDomElement feesElem = propertiesElement.firstChildElement( QStringLiteral( "WMSFees" ) );
588588
QDomElement wmsFeesElem = doc.createElement( QStringLiteral( "Fees" ) );
589-
QDomText wmsFeesText = doc.createTextNode( QStringLiteral( "conditions unknown" ) ); // default value if access conditions are unknown
589+
QDomText wmsFeesText = doc.createTextNode( QStringLiteral( "None" ) ); // default value if access conditions are unknown
590590
if ( !feesElem.isNull() && !feesElem.text().isEmpty() )
591591
{
592592
wmsFeesText = doc.createTextNode( feesElem.text() );

src/server/qgswmsserver.cpp

+92-5
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,12 @@ QDomDocument QgsWmsServer::getCapabilities( const QString& version, bool fullPro
427427
hrefString = serviceUrl();
428428
}
429429

430+
//href needs to be a prefix
431+
if ( !hrefString.endsWith( "?" ) && !hrefString.endsWith( "&" ) )
432+
{
433+
hrefString.append( hrefString.contains( "?" ) ? "&" : "?" );
434+
}
435+
430436
if ( version == QLatin1String( "1.1.1" ) )
431437
{
432438
doc = QDomDocument( QStringLiteral( "WMT_MS_Capabilities SYSTEM 'http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd'" ) ); //WMS 1.1.1 needs DOCTYPE "SYSTEM http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd"
@@ -560,7 +566,7 @@ QDomDocument QgsWmsServer::getCapabilities( const QString& version, bool fullPro
560566

561567
//Exception element is mandatory
562568
elem = doc.createElement( QStringLiteral( "Exception" ) );
563-
appendFormats( doc, elem, QStringList() << ( version == QLatin1String( "1.1.1" ) ? "application/vnd.ogc.se_xml" : "text/xml" ) );
569+
appendFormats( doc, elem, QStringList() << ( version == QLatin1String( "1.1.1" ) ? "application/vnd.ogc.se_xml" : "XML" ) );
564570
capabilityElement.appendChild( elem );
565571

566572
//UserDefinedSymbolization element
@@ -694,6 +700,10 @@ static QgsRectangle _parseBBOX( const QString &bboxStr, bool &ok )
694700
}
695701

696702
ok = true;
703+
if ( d[2] <= d[0] || d[3] <= d[1] )
704+
{
705+
throw QgsMapServiceException( "InvalidParameterValue", "BBOX is empty" );
706+
}
697707
return QgsRectangle( d[0], d[1], d[2], d[3] );
698708
}
699709

@@ -1460,6 +1470,17 @@ QImage* QgsWmsServer::getMap( HitTest* hitTest )
14601470
// theImage->save( QDir::tempPath() + QDir::separator() + "lastrender.png" );
14611471
//#endif
14621472

1473+
thePainter.end();
1474+
1475+
//test if width / height ratio of image is the same as the ratio of WIDTH / HEIGHT parameters. If not, the image has to be scaled (required by WMS spec)
1476+
int widthParam = mParameters.value( "WIDTH", "0" ).toInt();
1477+
int heightParam = mParameters.value( "HEIGHT", "0" ).toInt();
1478+
if ( widthParam != theImage->width() || heightParam != theImage->height() )
1479+
{
1480+
//scale image
1481+
*theImage = theImage->scaled( widthParam, heightParam, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
1482+
}
1483+
14631484
return theImage;
14641485
}
14651486

@@ -1581,7 +1602,6 @@ int QgsWmsServer::getFeatureInfo( QDomDocument& result, const QString& version )
15811602
QgsRectangle mapExtent = mMapRenderer->extent();
15821603
double scaleDenominator = scaleCalc.calculate( mapExtent, outputImage->width() );
15831604
mConfigParser->setScaleDenominator( scaleDenominator );
1584-
delete outputImage; //no longer needed for feature info
15851605

15861606
//read FEATURE_COUNT
15871607
int featureCount = 1;
@@ -1621,6 +1641,16 @@ int QgsWmsServer::getFeatureInfo( QDomDocument& result, const QString& version )
16211641
j = -1;
16221642
}
16231643

1644+
//In case the output image is distorted (WIDTH/HEIGHT ratio not equal to BBOX width/height), I and J need to be adapted as well
1645+
int widthParam = mParameters.value( "WIDTH", "-1" ).toInt();
1646+
int heightParam = mParameters.value( "HEIGHT", "-1" ).toInt();
1647+
if (( i != -1 && j != -1 && widthParam != -1 && heightParam != -1 ) && ( widthParam != outputImage->width() || heightParam != outputImage->height() ) )
1648+
{
1649+
i *= ( outputImage->width() / ( double )widthParam );
1650+
j *= ( outputImage->height() / ( double )heightParam );
1651+
}
1652+
delete outputImage; //no longer needed for feature info
1653+
16241654
//Normally, I/J or X/Y are mandatory parameters.
16251655
//However, in order to make attribute only queries via the FILTER parameter, it is allowed to skip them if the FILTER parameter is there
16261656

@@ -1962,6 +1992,29 @@ QImage* QgsWmsServer::createImage( int width, int height ) const
19621992
}
19631993
}
19641994

1995+
//Adapt width / height if the aspect ratio does not correspond with the BBOX.
1996+
//Required by WMS spec. 1.3.
1997+
bool bboxOk;
1998+
QgsRectangle mapExtent = _parseBBOX( mParameters.value( "BBOX" ), bboxOk );
1999+
if ( bboxOk )
2000+
{
2001+
double mapWidthHeightRatio = mapExtent.width() / mapExtent.height();
2002+
double imageWidthHeightRatio = ( double )width / ( double )height;
2003+
if ( !qgsDoubleNear( mapWidthHeightRatio, imageWidthHeightRatio, 0.0001 ) )
2004+
{
2005+
if ( mapWidthHeightRatio >= imageWidthHeightRatio )
2006+
{
2007+
//decrease image height
2008+
height = width / mapWidthHeightRatio;
2009+
}
2010+
else
2011+
{
2012+
//decrease image width
2013+
width = height * mapWidthHeightRatio;
2014+
}
2015+
}
2016+
}
2017+
19652018
if ( width < 0 || height < 0 )
19662019
{
19672020
return nullptr;
@@ -1978,6 +2031,19 @@ QImage* QgsWmsServer::createImage( int width, int height ) const
19782031
//transparent parameter
19792032
bool transparent = mParameters.value( QStringLiteral( "TRANSPARENT" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
19802033

2034+
//background color
2035+
QString bgColorString = mParameters.value( "BGCOLOR" );
2036+
if ( bgColorString.startsWith( "0x", Qt::CaseInsensitive ) )
2037+
{
2038+
bgColorString.replace( 0, 2, "#" );
2039+
}
2040+
QColor backgroundColor;
2041+
backgroundColor.setNamedColor( bgColorString );
2042+
if ( !backgroundColor.isValid() )
2043+
{
2044+
backgroundColor = QColor( Qt::white );
2045+
}
2046+
19812047
//use alpha channel only if necessary because it slows down performance
19822048
if ( transparent && !jpeg )
19832049
{
@@ -1987,7 +2053,7 @@ QImage* QgsWmsServer::createImage( int width, int height ) const
19872053
else
19882054
{
19892055
theImage = new QImage( width, height, QImage::Format_RGB32 );
1990-
theImage->fill( qRgb( 255, 255, 255 ) );
2056+
theImage->fill( backgroundColor );
19912057
}
19922058

19932059
if ( !theImage )
@@ -2023,17 +2089,32 @@ int QgsWmsServer::configureMapRender( const QPaintDevice* paintDevice ) const
20232089
mMapRenderer->setOutputSize( QSize( paintDevice->width(), paintDevice->height() ), paintDevice->logicalDpiX() );
20242090

20252091
//map extent
2026-
bool bboxOk;
2027-
QgsRectangle mapExtent = _parseBBOX( mParameters.value( QStringLiteral( "BBOX" ), QStringLiteral( "0,0,0,0" ) ), bboxOk );
2092+
bool bboxOk = true;
2093+
QgsRectangle mapExtent;
2094+
if ( mParameters.contains( "BBOX" ) )
2095+
{
2096+
mapExtent = _parseBBOX( mParameters.value( QStringLiteral( "BBOX" ), QStringLiteral( "0,0,0,0" ) ), bboxOk );
2097+
}
2098+
20282099
if ( !bboxOk )
20292100
{
20302101
//throw a service exception
20312102
throw QgsMapServiceException( QStringLiteral( "InvalidParameterValue" ), QStringLiteral( "Invalid BBOX parameter" ) );
20322103
}
20332104

2105+
if ( mParameters.contains( "BBOX" ) && mapExtent.isEmpty() )
2106+
{
2107+
throw QgsMapServiceException( "InvalidParameterValue", "BBOX is empty" );
2108+
}
2109+
20342110
QgsUnitTypes::DistanceUnit mapUnits = QgsUnitTypes::DistanceDegrees;
20352111

20362112
QString crs = mParameters.value( QStringLiteral( "CRS" ), mParameters.value( QStringLiteral( "SRS" ) ) );
2113+
if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
2114+
{
2115+
crs = QString( "EPSG:4326" );
2116+
mapExtent.invert();
2117+
}
20372118

20382119
QgsCoordinateReferenceSystem outputCRS;
20392120

@@ -2160,6 +2241,12 @@ bool QgsWmsServer::infoPointToMapCoordinates( int i, int j, QgsPoint* infoPoint,
21602241
return false;
21612242
}
21622243

2244+
//check if i, j are in the pixel range of the image
2245+
if ( i < 0 || i > mapRenderer->width() || j < 0 || j > mapRenderer->height() )
2246+
{
2247+
throw QgsMapServiceException( "InvalidPoint", "I/J parameters not within the pixel range" );
2248+
}
2249+
21632250
double xRes = mapRenderer->extent().width() / mapRenderer->width();
21642251
double yRes = mapRenderer->extent().height() / mapRenderer->height();
21652252
infoPoint->setX( mapRenderer->extent().xMinimum() + i * xRes + xRes / 2.0 );

tests/src/python/test_qgsserver_accesscontrol.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def test_wms_getmap(self):
306306
"LAYERS": "Country,Hello",
307307
"STYLES": "",
308308
"FORMAT": "image/png",
309-
"BBOX": "-16817707,-4710778,5696513,14587125",
309+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
310310
"HEIGHT": "500",
311311
"WIDTH": "500",
312312
"SRS": "EPSG:3857"
@@ -323,7 +323,7 @@ def test_wms_getmap(self):
323323
"LAYERS": "Hello",
324324
"STYLES": "",
325325
"FORMAT": "image/png",
326-
"BBOX": "-16817707,-4710778,5696513,14587125",
326+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
327327
"HEIGHT": "500",
328328
"WIDTH": "500",
329329
"SRS": "EPSG:3857"
@@ -339,7 +339,7 @@ def test_wms_getmap(self):
339339
"LAYERS": "Country",
340340
"STYLES": "",
341341
"FORMAT": "image/png",
342-
"BBOX": "-16817707,-4710778,5696513,14587125",
342+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
343343
"HEIGHT": "500",
344344
"WIDTH": "500",
345345
"SRS": "EPSG:3857"
@@ -363,7 +363,7 @@ def test_wms_getfeatureinfo_hello(self):
363363
"QUERY_LAYERS": "Hello",
364364
"STYLES": "",
365365
"FORMAT": "image/png",
366-
"BBOX": "-16817707,-4710778,5696513,14587125",
366+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
367367
"HEIGHT": "500",
368368
"WIDTH": "500",
369369
"SRS": "EPSG:3857",
@@ -402,7 +402,7 @@ def test_wms_getfeatureinfo_hello2(self):
402402
"QUERY_LAYERS": "Hello",
403403
"STYLES": "",
404404
"FORMAT": "image/png",
405-
"BBOX": "-16817707,-4710778,5696513,14587125",
405+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
406406
"HEIGHT": "500",
407407
"WIDTH": "500",
408408
"SRS": "EPSG:3857",
@@ -432,7 +432,7 @@ def test_wms_getfeatureinfo_country(self):
432432
"QUERY_LAYERS": "Country",
433433
"STYLES": "",
434434
"FORMAT": "image/png",
435-
"BBOX": "-16817707,-4710778,5696513,14587125",
435+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
436436
"HEIGHT": "500",
437437
"WIDTH": "500",
438438
"SRS": "EPSG:3857",
@@ -854,7 +854,7 @@ def test_wms_getmap_subsetstring(self):
854854
"LAYERS": "Country,Hello_SubsetString",
855855
"STYLES": "",
856856
"FORMAT": "image/png",
857-
"BBOX": "-16817707,-4710778,5696513,14587125",
857+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
858858
"HEIGHT": "500",
859859
"WIDTH": "500",
860860
"SRS": "EPSG:3857"
@@ -871,7 +871,7 @@ def test_wms_getmap_subsetstring(self):
871871
"LAYERS": "Hello_SubsetString",
872872
"STYLES": "",
873873
"FORMAT": "image/png",
874-
"BBOX": "-16817707,-4710778,5696513,14587125",
874+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
875875
"HEIGHT": "500",
876876
"WIDTH": "500",
877877
"SRS": "EPSG:3857"
@@ -894,7 +894,7 @@ def test_wms_getmap_subsetstring_with_filter(self):
894894
"FILTER": "Hello_Filter_SubsetString:\"pkuid\" IN ( 7 , 8 )",
895895
"STYLES": "",
896896
"FORMAT": "image/png",
897-
"BBOX": "-16817707,-4710778,5696513,14587125",
897+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
898898
"HEIGHT": "500",
899899
"WIDTH": "500",
900900
"SRS": "EPSG:3857"
@@ -912,7 +912,7 @@ def test_wms_getmap_subsetstring_with_filter(self):
912912
"FILTER": "Hello_Filter_SubsetString:\"pkuid\" IN ( 7 , 8 )",
913913
"STYLES": "",
914914
"FORMAT": "image/png",
915-
"BBOX": "-16817707,-4710778,5696513,14587125",
915+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
916916
"HEIGHT": "500",
917917
"WIDTH": "500",
918918
"SRS": "EPSG:3857"
@@ -931,7 +931,7 @@ def test_wms_getmap_projectsubsetstring(self):
931931
"LAYERS": "Hello_Project_SubsetString",
932932
"STYLES": "",
933933
"FORMAT": "image/png",
934-
"BBOX": "-16817707,-4710778,5696513,14587125",
934+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
935935
"HEIGHT": "500",
936936
"WIDTH": "500",
937937
"SRS": "EPSG:3857"
@@ -948,7 +948,7 @@ def test_wms_getmap_projectsubsetstring(self):
948948
"LAYERS": "Hello_Project_SubsetString",
949949
"STYLES": "",
950950
"FORMAT": "image/png",
951-
"BBOX": "-16817707,-4710778,5696513,14587125",
951+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
952952
"HEIGHT": "500",
953953
"WIDTH": "500",
954954
"SRS": "EPSG:3857"
@@ -966,7 +966,7 @@ def test_wms_getfeatureinfo_subsetstring(self):
966966
"QUERY_LAYERS": "Hello_SubsetString",
967967
"STYLES": "",
968968
"FORMAT": "image/png",
969-
"BBOX": "-16817707,-4710778,5696513,14587125",
969+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
970970
"HEIGHT": "500",
971971
"WIDTH": "500",
972972
"SRS": "EPSG:3857",
@@ -1002,7 +1002,7 @@ def test_wms_getfeatureinfo_subsetstring2(self):
10021002
"QUERY_LAYERS": "Hello_SubsetString",
10031003
"STYLES": "",
10041004
"FORMAT": "image/png",
1005-
"BBOX": "-16817707,-4710778,5696513,14587125",
1005+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
10061006
"HEIGHT": "500",
10071007
"WIDTH": "500",
10081008
"SRS": "EPSG:3857",
@@ -1038,7 +1038,7 @@ def test_wms_getfeatureinfo_projectsubsetstring(self):
10381038
"QUERY_LAYERS": "Hello_Project_SubsetString",
10391039
"STYLES": "",
10401040
"FORMAT": "image/png",
1041-
"BBOX": "-16817707,-4710778,5696513,14587125",
1041+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
10421042
"HEIGHT": "500",
10431043
"WIDTH": "500",
10441044
"SRS": "EPSG:3857",
@@ -1147,7 +1147,7 @@ def test_wms_getfeatureinfo_subsetstring_with_filter(self):
11471147
"FILTER": "Hello_Filter_SubsetString:\"pkuid\" IN ( 7 , 8 )",
11481148
"STYLES": "",
11491149
"FORMAT": "image/png",
1150-
"BBOX": "-16817707,-4710778,5696513,14587125",
1150+
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
11511151
"HEIGHT": "500",
11521152
"WIDTH": "500",
11531153
"SRS": "EPSG:3857",

tests/testdata/qgis_server/getcapabilities.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,12 @@ Content-Type: text/xml; charset=utf-8
9797
</qgs:GetStyles>
9898
</Request>
9999
<Exception>
100-
<Format>text/xml</Format>
100+
<Format>XML</Format>
101101
</Exception>
102102
<Layer queryable="1">
103103
<Name>QGIS Test Project</Name>
104104
<Title>QGIS Test Project</Title>
105+
<CRS>CRS:84</CRS>
105106
<CRS>EPSG:4326</CRS>
106107
<CRS>EPSG:3857</CRS>
107108
<EX_GeographicBoundingBox>
@@ -116,6 +117,7 @@ Content-Type: text/xml; charset=utf-8
116117
<Name>testlayer èé</Name>
117118
<Title>A test vector layer</Title>
118119
<Abstract>A test vector layer with unicode òà</Abstract>
120+
<CRS>CRS:84</CRS>
119121
<CRS>EPSG:4326</CRS>
120122
<CRS>EPSG:3857</CRS>
121123
<EX_GeographicBoundingBox>

0 commit comments

Comments
 (0)