Skip to content

Commit

Permalink
Merge pull request #3862 from rldhont/release-2_14-wms-130-compliance
Browse files Browse the repository at this point in the history
Backport 214 wms 130 compliance
  • Loading branch information
rldhont committed Dec 13, 2016
2 parents 02d70fa + a1a4ad9 commit f8e2b85
Show file tree
Hide file tree
Showing 17 changed files with 290 additions and 36 deletions.
12 changes: 11 additions & 1 deletion src/server/qgsconfigparserutils.cpp
Expand Up @@ -65,6 +65,9 @@ void QgsConfigParserUtils::appendCRSElementsToLayer( QDomElement& layerElement,
appendCRSElementToLayer( layerElement, CRSPrecedingElement, crs, doc );
}
}

//Support for CRS:84 is mandatory (equals EPSG:4326 with reversed axis)
appendCRSElementToLayer( layerElement, CRSPrecedingElement, QString( "CRS:84" ), doc );
}

void QgsConfigParserUtils::appendCRSElementToLayer( QDomElement& layerElement, const QDomElement& precedingElement,
Expand All @@ -77,14 +80,21 @@ void QgsConfigParserUtils::appendCRSElementToLayer( QDomElement& layerElement, c
layerElement.insertAfter( crsElement, precedingElement );
}

void QgsConfigParserUtils::appendLayerBoundingBoxes( QDomElement& layerElem, QDomDocument& doc, const QgsRectangle& layerExtent,
void QgsConfigParserUtils::appendLayerBoundingBoxes( QDomElement& layerElem, QDomDocument& doc, const QgsRectangle& lExtent,
const QgsCoordinateReferenceSystem& layerCRS, const QStringList &crsList, const QStringList& constrainedCrsList )
{
if ( layerElem.isNull() )
{
return;
}

QgsRectangle layerExtent = lExtent;
if ( qgsDoubleNear( layerExtent.xMinimum(), layerExtent.xMaximum() ) || qgsDoubleNear( layerExtent.yMinimum(), layerExtent.yMaximum() ) )
{
//layer bbox cannot be empty
layerExtent.grow( 0.000001 );
}

const QgsCoordinateReferenceSystem& wgs84 = QgsCRSCache::instance()->crsByAuthId( GEO_EPSG_CRS_AUTHID );

QString version = doc.documentElement().attribute( "version" );
Expand Down
2 changes: 1 addition & 1 deletion src/server/qgsserverprojectparser.cpp
Expand Up @@ -595,7 +595,7 @@ void QgsServerProjectParser::serviceCapabilities( QDomElement& parentElement, QD
//Fees
QDomElement feesElem = propertiesElement.firstChildElement( "WMSFees" );
QDomElement wmsFeesElem = doc.createElement( "Fees" );
QDomText wmsFeesText = doc.createTextNode( "conditions unknown" ); // default value if access conditions are unknown
QDomText wmsFeesText = doc.createTextNode( "None" ); // default value if access conditions are unknown
if ( !feesElem.isNull() && !feesElem.text().isEmpty() )
{
wmsFeesText = doc.createTextNode( feesElem.text() );
Expand Down
130 changes: 118 additions & 12 deletions src/server/qgswmsserver.cpp
Expand Up @@ -147,13 +147,30 @@ void QgsWMSServer::executeRequest()
}

//version
QString version = mParameters.value( "VERSION", "1.3.0" );
QString version = "1.3.0";
if ( mParameters.contains( "VERSION" ) )
{
version = mParameters.value( "VERSION" );
}
else if ( mParameters.contains( "WMTVER" ) ) //WMTVER needs to be supported by WMS 1.1.1 for backwards compatibility with WMS 1.0.0
{
version = mParameters.value( "WMTVER" );
}

bool getProjectSettings = ( request.compare( "GetProjectSettings", Qt::CaseInsensitive ) == 0 );
if ( getProjectSettings )
{
version = "1.3.0"; //getProjectSettings extends WMS 1.3.0 capabilities
}

if ( version == "1.1.1" )
{
if ( request.compare( "capabilities", Qt::CaseInsensitive ) == 0 )
{
request = QString( "GetCapabilities" );
}
}

//GetCapabilities
if ( request.compare( "GetCapabilities", Qt::CaseInsensitive ) == 0 || getProjectSettings )
{
Expand Down Expand Up @@ -423,6 +440,12 @@ QDomDocument QgsWMSServer::getCapabilities( QString version, bool fullProjectInf
hrefString = serviceUrl();
}

//href needs to be a prefix
if ( !hrefString.endsWith( "?" ) && !hrefString.endsWith( "&" ) )
{
hrefString.append( hrefString.contains( "?" ) ? "&" : "?" );
}

if ( version == "1.1.1" )
{
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"
Expand Down Expand Up @@ -556,7 +579,7 @@ QDomDocument QgsWMSServer::getCapabilities( QString version, bool fullProjectInf

//Exception element is mandatory
elem = doc.createElement( "Exception" );
appendFormats( doc, elem, QStringList() << ( version == "1.1.1" ? "application/vnd.ogc.se_xml" : "text/xml" ) );
appendFormats( doc, elem, QStringList() << ( version == "1.1.1" ? "application/vnd.ogc.se_xml" : "XML" ) );
capabilityElement.appendChild( elem );

//UserDefinedSymbolization element
Expand Down Expand Up @@ -690,6 +713,10 @@ static QgsRectangle _parseBBOX( const QString &bboxStr, bool &ok )
}

ok = true;
if ( d[2] <= d[0] || d[3] <= d[1] )
{
throw QgsMapServiceException( "InvalidParameterValue", "BBOX is empty" );
}
return QgsRectangle( d[0], d[1], d[2], d[3] );
}

Expand Down Expand Up @@ -822,8 +849,6 @@ QImage* QgsWMSServer::getLegendGraphics()
}
QgsLayerTreeModel legendModel( &rootGroup );

QList<QgsLayerTreeNode*> rootChildren = rootGroup.children();

if ( scaleDenominator > 0 )
legendModel.setLegendFilterByScale( scaleDenominator );

Expand Down Expand Up @@ -863,7 +888,7 @@ QImage* QgsWMSServer::getLegendGraphics()
}

// find out DPI
QImage* tmpImage = createImage( 1, 1 );
QImage* tmpImage = createImage( 1, 1, false );
if ( !tmpImage )
return nullptr;
qreal dpmm = tmpImage->dotsPerMeterX() / 1000.0;
Expand Down Expand Up @@ -893,7 +918,7 @@ QImage* QgsWMSServer::getLegendGraphics()
if ( !rule.isEmpty() )
{
//create second image with the right dimensions
QImage* paintImage = createImage( ruleSymbolWidth, ruleSymbolHeight );
QImage* paintImage = createImage( ruleSymbolWidth, ruleSymbolHeight, false );

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

QList<QgsLayerTreeNode*> rootChildren = rootGroup.children();
Q_FOREACH ( QgsLayerTreeNode* node, rootChildren )
{
if ( QgsLayerTree::isLayer( node ) )
Expand Down Expand Up @@ -954,7 +980,7 @@ QImage* QgsWMSServer::getLegendGraphics()
QSizeF minSize = legendRenderer.minimumSize();
QSize s( minSize.width() * dpmm, minSize.height() * dpmm );

QImage* paintImage = createImage( s.width(), s.height() );
QImage* paintImage = createImage( s.width(), s.height(), false );

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

thePainter.end();

//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)
int widthParam = mParameters.value( "WIDTH", "0" ).toInt();
int heightParam = mParameters.value( "HEIGHT", "0" ).toInt();
if ( widthParam != theImage->width() || heightParam != theImage->height() )
{
//scale image
*theImage = theImage->scaled( widthParam, heightParam, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
}

return theImage;
}

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

//read FEATURE_COUNT
int featureCount = 1;
Expand Down Expand Up @@ -1610,6 +1646,16 @@ int QgsWMSServer::getFeatureInfo( QDomDocument& result, const QString& version )
j = -1;
}

//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
int widthParam = mParameters.value( "WIDTH", "-1" ).toInt();
int heightParam = mParameters.value( "HEIGHT", "-1" ).toInt();
if (( i != -1 && j != -1 && widthParam != -1 && heightParam != -1 ) && ( widthParam != outputImage->width() || heightParam != outputImage->height() ) )
{
i *= ( outputImage->width() / ( double )widthParam );
j *= ( outputImage->height() / ( double )heightParam );
}
delete outputImage; //no longer needed for feature info

//Normally, I/J or X/Y are mandatory parameters.
//However, in order to make attribute only queries via the FILTER parameter, it is allowed to skip them if the FILTER parameter is there

Expand Down Expand Up @@ -1926,7 +1972,7 @@ QImage* QgsWMSServer::initializeRendering( QStringList& layersList, QStringList&
return theImage;
}

QImage* QgsWMSServer::createImage( int width, int height ) const
QImage* QgsWMSServer::createImage( int width, int height, bool useBbox ) const
{
bool conversionSuccess;

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

//Adapt width / height if the aspect ratio does not correspond with the BBOX.
//Required by WMS spec. 1.3.
if ( useBbox )
{
bool bboxOk;
QgsRectangle mapExtent = _parseBBOX( mParameters.value( "BBOX" ), bboxOk );
if ( bboxOk )
{
double mapWidthHeightRatio = mapExtent.width() / mapExtent.height();
double imageWidthHeightRatio = ( double )width / ( double )height;
if ( !qgsDoubleNear( mapWidthHeightRatio, imageWidthHeightRatio, 0.0001 ) )
{
if ( mapWidthHeightRatio >= imageWidthHeightRatio )
{
//decrease image height
height = width / mapWidthHeightRatio;
}
else
{
//decrease image width
width = height * mapWidthHeightRatio;
}
}
}
}

if ( width < 0 || height < 0 )
{
return nullptr;
Expand All @@ -1962,6 +2034,19 @@ QImage* QgsWMSServer::createImage( int width, int height ) const
//transparent parameter
bool transparent = mParameters.value( "TRANSPARENT" ).compare( "true", Qt::CaseInsensitive ) == 0;

//background color
QString bgColorString = mParameters.value( "BGCOLOR" );
if ( bgColorString.startsWith( "0x", Qt::CaseInsensitive ) )
{
bgColorString.replace( 0, 2, "#" );
}
QColor backgroundColor;
backgroundColor.setNamedColor( bgColorString );
if ( !backgroundColor.isValid() )
{
backgroundColor = QColor( Qt::white );
}

//use alpha channel only if necessary because it slows down performance
if ( transparent && !jpeg )
{
Expand All @@ -1971,7 +2056,7 @@ QImage* QgsWMSServer::createImage( int width, int height ) const
else
{
theImage = new QImage( width, height, QImage::Format_RGB32 );
theImage->fill( qRgb( 255, 255, 255 ) );
theImage->fill( backgroundColor );
}

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

//map extent
bool bboxOk;
QgsRectangle mapExtent = _parseBBOX( mParameters.value( "BBOX", "0,0,0,0" ), bboxOk );
bool bboxOk = true;
QgsRectangle mapExtent;
if ( mParameters.contains( "BBOX" ) )
{
mapExtent = _parseBBOX( mParameters.value( "BBOX", "0,0,0,0" ), bboxOk );
}

if ( !bboxOk )
{
//throw a service exception
throw QgsMapServiceException( "InvalidParameterValue", "Invalid BBOX parameter" );
}

if ( mParameters.contains( "BBOX" ) && mapExtent.isEmpty() )
{
throw QgsMapServiceException( "InvalidParameterValue", "BBOX is empty" );
}

QGis::UnitType mapUnits = QGis::Degrees;

QString crs = mParameters.value( "CRS", mParameters.value( "SRS" ) );
if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
{
crs = QString( "EPSG:4326" );
mapExtent.invert();
}

QgsCoordinateReferenceSystem outputCRS;

Expand Down Expand Up @@ -2154,6 +2254,12 @@ bool QgsWMSServer::infoPointToMapCoordinates( int i, int j, QgsPoint* infoPoint,
return false;
}

//check if i, j are in the pixel range of the image
if ( i < 0 || i > mapRenderer->width() || j < 0 || j > mapRenderer->height() )
{
throw QgsMapServiceException( "InvalidPoint", "I/J parameters not within the pixel range" );
}

double xRes = mapRenderer->extent().width() / mapRenderer->width();
double yRes = mapRenderer->extent().height() / mapRenderer->height();
infoPoint->setX( mapRenderer->extent().xMinimum() + i * xRes + xRes / 2.0 );
Expand Down
6 changes: 4 additions & 2 deletions src/server/qgswmsserver.h
Expand Up @@ -133,9 +133,11 @@ class QgsWMSServer: public QgsOWSServer
/** Creates a QImage from the HEIGHT and WIDTH parameters
@param width image width (or -1 if width should be taken from WIDTH wms parameter)
@param height image height (or -1 if height should be taken from HEIGHT wms parameter)
@param useBbox flag to indicate if the BBOX has to be used to adapt aspect ratio
@return 0 in case of error*/
QImage* createImage( int width = -1, int height = -1 ) const;
/** Configures mMapRenderer to the parameters
QImage* createImage( int width = -1, int height = -1, bool useBbox = true ) const;

/** Configures mapSettings to the parameters
HEIGHT, WIDTH, BBOX, CRS.
@param paintDevice the device that is used for painting (for dpi)
@return 0 in case of success*/
Expand Down

0 comments on commit f8e2b85

Please sign in to comment.