Skip to content

Commit f8e2b85

Browse files
authored
Merge pull request #3862 from rldhont/release-2_14-wms-130-compliance
Backport 214 wms 130 compliance
2 parents 02d70fa + a1a4ad9 commit f8e2b85

File tree

17 files changed

+290
-36
lines changed

17 files changed

+290
-36
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
const QgsCoordinateReferenceSystem& wgs84 = QgsCRSCache::instance()->crsByAuthId( GEO_EPSG_CRS_AUTHID );
8999

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

src/server/qgsserverprojectparser.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ void QgsServerProjectParser::serviceCapabilities( QDomElement& parentElement, QD
595595
//Fees
596596
QDomElement feesElem = propertiesElement.firstChildElement( "WMSFees" );
597597
QDomElement wmsFeesElem = doc.createElement( "Fees" );
598-
QDomText wmsFeesText = doc.createTextNode( "conditions unknown" ); // default value if access conditions are unknown
598+
QDomText wmsFeesText = doc.createTextNode( "None" ); // default value if access conditions are unknown
599599
if ( !feesElem.isNull() && !feesElem.text().isEmpty() )
600600
{
601601
wmsFeesText = doc.createTextNode( feesElem.text() );

src/server/qgswmsserver.cpp

+118-12
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,30 @@ void QgsWMSServer::executeRequest()
147147
}
148148

149149
//version
150-
QString version = mParameters.value( "VERSION", "1.3.0" );
150+
QString version = "1.3.0";
151+
if ( mParameters.contains( "VERSION" ) )
152+
{
153+
version = mParameters.value( "VERSION" );
154+
}
155+
else if ( mParameters.contains( "WMTVER" ) ) //WMTVER needs to be supported by WMS 1.1.1 for backwards compatibility with WMS 1.0.0
156+
{
157+
version = mParameters.value( "WMTVER" );
158+
}
159+
151160
bool getProjectSettings = ( request.compare( "GetProjectSettings", Qt::CaseInsensitive ) == 0 );
152161
if ( getProjectSettings )
153162
{
154163
version = "1.3.0"; //getProjectSettings extends WMS 1.3.0 capabilities
155164
}
156165

166+
if ( version == "1.1.1" )
167+
{
168+
if ( request.compare( "capabilities", Qt::CaseInsensitive ) == 0 )
169+
{
170+
request = QString( "GetCapabilities" );
171+
}
172+
}
173+
157174
//GetCapabilities
158175
if ( request.compare( "GetCapabilities", Qt::CaseInsensitive ) == 0 || getProjectSettings )
159176
{
@@ -423,6 +440,12 @@ QDomDocument QgsWMSServer::getCapabilities( QString version, bool fullProjectInf
423440
hrefString = serviceUrl();
424441
}
425442

443+
//href needs to be a prefix
444+
if ( !hrefString.endsWith( "?" ) && !hrefString.endsWith( "&" ) )
445+
{
446+
hrefString.append( hrefString.contains( "?" ) ? "&" : "?" );
447+
}
448+
426449
if ( version == "1.1.1" )
427450
{
428451
doc = QDomDocument( "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"
@@ -556,7 +579,7 @@ QDomDocument QgsWMSServer::getCapabilities( QString version, bool fullProjectInf
556579

557580
//Exception element is mandatory
558581
elem = doc.createElement( "Exception" );
559-
appendFormats( doc, elem, QStringList() << ( version == "1.1.1" ? "application/vnd.ogc.se_xml" : "text/xml" ) );
582+
appendFormats( doc, elem, QStringList() << ( version == "1.1.1" ? "application/vnd.ogc.se_xml" : "XML" ) );
560583
capabilityElement.appendChild( elem );
561584

562585
//UserDefinedSymbolization element
@@ -690,6 +713,10 @@ static QgsRectangle _parseBBOX( const QString &bboxStr, bool &ok )
690713
}
691714

692715
ok = true;
716+
if ( d[2] <= d[0] || d[3] <= d[1] )
717+
{
718+
throw QgsMapServiceException( "InvalidParameterValue", "BBOX is empty" );
719+
}
693720
return QgsRectangle( d[0], d[1], d[2], d[3] );
694721
}
695722

@@ -822,8 +849,6 @@ QImage* QgsWMSServer::getLegendGraphics()
822849
}
823850
QgsLayerTreeModel legendModel( &rootGroup );
824851

825-
QList<QgsLayerTreeNode*> rootChildren = rootGroup.children();
826-
827852
if ( scaleDenominator > 0 )
828853
legendModel.setLegendFilterByScale( scaleDenominator );
829854

@@ -863,7 +888,7 @@ QImage* QgsWMSServer::getLegendGraphics()
863888
}
864889

865890
// find out DPI
866-
QImage* tmpImage = createImage( 1, 1 );
891+
QImage* tmpImage = createImage( 1, 1, false );
867892
if ( !tmpImage )
868893
return nullptr;
869894
qreal dpmm = tmpImage->dotsPerMeterX() / 1000.0;
@@ -893,7 +918,7 @@ QImage* QgsWMSServer::getLegendGraphics()
893918
if ( !rule.isEmpty() )
894919
{
895920
//create second image with the right dimensions
896-
QImage* paintImage = createImage( ruleSymbolWidth, ruleSymbolHeight );
921+
QImage* paintImage = createImage( ruleSymbolWidth, ruleSymbolHeight, false );
897922

898923
//go through the items a second time for painting
899924
QPainter p( paintImage );
@@ -915,6 +940,7 @@ QImage* QgsWMSServer::getLegendGraphics()
915940
return paintImage;
916941
}
917942

943+
QList<QgsLayerTreeNode*> rootChildren = rootGroup.children();
918944
Q_FOREACH ( QgsLayerTreeNode* node, rootChildren )
919945
{
920946
if ( QgsLayerTree::isLayer( node ) )
@@ -954,7 +980,7 @@ QImage* QgsWMSServer::getLegendGraphics()
954980
QSizeF minSize = legendRenderer.minimumSize();
955981
QSize s( minSize.width() * dpmm, minSize.height() * dpmm );
956982

957-
QImage* paintImage = createImage( s.width(), s.height() );
983+
QImage* paintImage = createImage( s.width(), s.height(), false );
958984

959985
QPainter p( paintImage );
960986
p.setRenderHint( QPainter::Antialiasing, true );
@@ -1449,6 +1475,17 @@ QImage* QgsWMSServer::getMap( HitTest* hitTest )
14491475
// theImage->save( QDir::tempPath() + QDir::separator() + "lastrender.png" );
14501476
//#endif
14511477

1478+
thePainter.end();
1479+
1480+
//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)
1481+
int widthParam = mParameters.value( "WIDTH", "0" ).toInt();
1482+
int heightParam = mParameters.value( "HEIGHT", "0" ).toInt();
1483+
if ( widthParam != theImage->width() || heightParam != theImage->height() )
1484+
{
1485+
//scale image
1486+
*theImage = theImage->scaled( widthParam, heightParam, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
1487+
}
1488+
14521489
return theImage;
14531490
}
14541491

@@ -1570,7 +1607,6 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result, const QString& version )
15701607
QgsRectangle mapExtent = mMapRenderer->extent();
15711608
double scaleDenominator = scaleCalc.calculate( mapExtent, outputImage->width() );
15721609
mConfigParser->setScaleDenominator( scaleDenominator );
1573-
delete outputImage; //no longer needed for feature info
15741610

15751611
//read FEATURE_COUNT
15761612
int featureCount = 1;
@@ -1610,6 +1646,16 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result, const QString& version )
16101646
j = -1;
16111647
}
16121648

1649+
//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
1650+
int widthParam = mParameters.value( "WIDTH", "-1" ).toInt();
1651+
int heightParam = mParameters.value( "HEIGHT", "-1" ).toInt();
1652+
if (( i != -1 && j != -1 && widthParam != -1 && heightParam != -1 ) && ( widthParam != outputImage->width() || heightParam != outputImage->height() ) )
1653+
{
1654+
i *= ( outputImage->width() / ( double )widthParam );
1655+
j *= ( outputImage->height() / ( double )heightParam );
1656+
}
1657+
delete outputImage; //no longer needed for feature info
1658+
16131659
//Normally, I/J or X/Y are mandatory parameters.
16141660
//However, in order to make attribute only queries via the FILTER parameter, it is allowed to skip them if the FILTER parameter is there
16151661

@@ -1926,7 +1972,7 @@ QImage* QgsWMSServer::initializeRendering( QStringList& layersList, QStringList&
19261972
return theImage;
19271973
}
19281974

1929-
QImage* QgsWMSServer::createImage( int width, int height ) const
1975+
QImage* QgsWMSServer::createImage( int width, int height, bool useBbox ) const
19301976
{
19311977
bool conversionSuccess;
19321978

@@ -1946,6 +1992,32 @@ QImage* QgsWMSServer::createImage( int width, int height ) const
19461992
}
19471993
}
19481994

1995+
//Adapt width / height if the aspect ratio does not correspond with the BBOX.
1996+
//Required by WMS spec. 1.3.
1997+
if ( useBbox )
1998+
{
1999+
bool bboxOk;
2000+
QgsRectangle mapExtent = _parseBBOX( mParameters.value( "BBOX" ), bboxOk );
2001+
if ( bboxOk )
2002+
{
2003+
double mapWidthHeightRatio = mapExtent.width() / mapExtent.height();
2004+
double imageWidthHeightRatio = ( double )width / ( double )height;
2005+
if ( !qgsDoubleNear( mapWidthHeightRatio, imageWidthHeightRatio, 0.0001 ) )
2006+
{
2007+
if ( mapWidthHeightRatio >= imageWidthHeightRatio )
2008+
{
2009+
//decrease image height
2010+
height = width / mapWidthHeightRatio;
2011+
}
2012+
else
2013+
{
2014+
//decrease image width
2015+
width = height * mapWidthHeightRatio;
2016+
}
2017+
}
2018+
}
2019+
}
2020+
19492021
if ( width < 0 || height < 0 )
19502022
{
19512023
return nullptr;
@@ -1962,6 +2034,19 @@ QImage* QgsWMSServer::createImage( int width, int height ) const
19622034
//transparent parameter
19632035
bool transparent = mParameters.value( "TRANSPARENT" ).compare( "true", Qt::CaseInsensitive ) == 0;
19642036

2037+
//background color
2038+
QString bgColorString = mParameters.value( "BGCOLOR" );
2039+
if ( bgColorString.startsWith( "0x", Qt::CaseInsensitive ) )
2040+
{
2041+
bgColorString.replace( 0, 2, "#" );
2042+
}
2043+
QColor backgroundColor;
2044+
backgroundColor.setNamedColor( bgColorString );
2045+
if ( !backgroundColor.isValid() )
2046+
{
2047+
backgroundColor = QColor( Qt::white );
2048+
}
2049+
19652050
//use alpha channel only if necessary because it slows down performance
19662051
if ( transparent && !jpeg )
19672052
{
@@ -1971,7 +2056,7 @@ QImage* QgsWMSServer::createImage( int width, int height ) const
19712056
else
19722057
{
19732058
theImage = new QImage( width, height, QImage::Format_RGB32 );
1974-
theImage->fill( qRgb( 255, 255, 255 ) );
2059+
theImage->fill( backgroundColor );
19752060
}
19762061

19772062
if ( !theImage )
@@ -2007,17 +2092,32 @@ int QgsWMSServer::configureMapRender( const QPaintDevice* paintDevice ) const
20072092
mMapRenderer->setOutputSize( QSize( paintDevice->width(), paintDevice->height() ), paintDevice->logicalDpiX() );
20082093

20092094
//map extent
2010-
bool bboxOk;
2011-
QgsRectangle mapExtent = _parseBBOX( mParameters.value( "BBOX", "0,0,0,0" ), bboxOk );
2095+
bool bboxOk = true;
2096+
QgsRectangle mapExtent;
2097+
if ( mParameters.contains( "BBOX" ) )
2098+
{
2099+
mapExtent = _parseBBOX( mParameters.value( "BBOX", "0,0,0,0" ), bboxOk );
2100+
}
2101+
20122102
if ( !bboxOk )
20132103
{
20142104
//throw a service exception
20152105
throw QgsMapServiceException( "InvalidParameterValue", "Invalid BBOX parameter" );
20162106
}
20172107

2108+
if ( mParameters.contains( "BBOX" ) && mapExtent.isEmpty() )
2109+
{
2110+
throw QgsMapServiceException( "InvalidParameterValue", "BBOX is empty" );
2111+
}
2112+
20182113
QGis::UnitType mapUnits = QGis::Degrees;
20192114

20202115
QString crs = mParameters.value( "CRS", mParameters.value( "SRS" ) );
2116+
if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
2117+
{
2118+
crs = QString( "EPSG:4326" );
2119+
mapExtent.invert();
2120+
}
20212121

20222122
QgsCoordinateReferenceSystem outputCRS;
20232123

@@ -2154,6 +2254,12 @@ bool QgsWMSServer::infoPointToMapCoordinates( int i, int j, QgsPoint* infoPoint,
21542254
return false;
21552255
}
21562256

2257+
//check if i, j are in the pixel range of the image
2258+
if ( i < 0 || i > mapRenderer->width() || j < 0 || j > mapRenderer->height() )
2259+
{
2260+
throw QgsMapServiceException( "InvalidPoint", "I/J parameters not within the pixel range" );
2261+
}
2262+
21572263
double xRes = mapRenderer->extent().width() / mapRenderer->width();
21582264
double yRes = mapRenderer->extent().height() / mapRenderer->height();
21592265
infoPoint->setX( mapRenderer->extent().xMinimum() + i * xRes + xRes / 2.0 );

src/server/qgswmsserver.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,11 @@ class QgsWMSServer: public QgsOWSServer
133133
/** Creates a QImage from the HEIGHT and WIDTH parameters
134134
@param width image width (or -1 if width should be taken from WIDTH wms parameter)
135135
@param height image height (or -1 if height should be taken from HEIGHT wms parameter)
136+
@param useBbox flag to indicate if the BBOX has to be used to adapt aspect ratio
136137
@return 0 in case of error*/
137-
QImage* createImage( int width = -1, int height = -1 ) const;
138-
/** Configures mMapRenderer to the parameters
138+
QImage* createImage( int width = -1, int height = -1, bool useBbox = true ) const;
139+
140+
/** Configures mapSettings to the parameters
139141
HEIGHT, WIDTH, BBOX, CRS.
140142
@param paintDevice the device that is used for painting (for dpi)
141143
@return 0 in case of success*/

0 commit comments

Comments
 (0)