From 76bef757c1b20519947b9610b0be4ca45ea3fbeb Mon Sep 17 00:00:00 2001 From: Radim Blazek Date: Mon, 13 Aug 2012 21:05:49 +0200 Subject: [PATCH] WCS GeoServer fixes --- src/providers/wcs/qgswcscapabilities.cpp | 62 +++++- src/providers/wcs/qgswcsprovider.cpp | 197 ++++++++++++++++-- src/providers/wcs/qgswcsprovider.h | 8 + .../src/providers/testqgswcspublicservers.cpp | 169 ++++++++------- tests/src/providers/testqgswcspublicservers.h | 8 +- 5 files changed, 339 insertions(+), 105 deletions(-) diff --git a/src/providers/wcs/qgswcscapabilities.cpp b/src/providers/wcs/qgswcscapabilities.cpp index 181250f08a28..2e2a4cfc3a20 100644 --- a/src/providers/wcs/qgswcscapabilities.cpp +++ b/src/providers/wcs/qgswcscapabilities.cpp @@ -211,7 +211,10 @@ bool QgsWcsCapabilities::retrieveServerCapabilities( ) { // 1.0.0 - VERSION // 1.1.0 - AcceptVersions (not supported by UMN Mapserver 6.0.3 - defaults to latest 1.1 - versions << "AcceptVersions=1.1,1.0" << "VERSION=1.0"; + // We prefer 1.0 because 1.1 has many issues, each server implements it in defferent + // way with various particularities + // It may happen that server supports 1.1.0 but gives error for 1.1 + versions << "VERSION=1.0.0" << "AcceptVersions=1.1.0,1.0.0"; } foreach ( QString v, versions ) @@ -271,8 +274,18 @@ bool QgsWcsCapabilities::describeCoverage( QString const &identifier, bool force if ( coverage->described && ! forceRefresh ) return true; - QString url = prepareUri( mUri.param( "url" ) ) + "SERVICE=WCS&REQUEST=DescribeCoverage&COVERAGE=" + coverage->identifier; - url += "&VERSION=" + mVersion; + QString url = prepareUri( mUri.param( "url" ) ) + "SERVICE=WCS&REQUEST=DescribeCoverage&VERSION=" + mVersion; + + if ( mVersion.startsWith( "1.0" ) ) + { + url += "&COVERAGE=" + coverage->identifier; + } + else if ( mVersion.startsWith( "1.1" ) ) + { + // in 1.1.0, 1.1.1, 1.1.2 the name of param is 'identifier' + // but in KVP 'identifiers' + url += "&IDENTIFIERS=" + coverage->identifier; + } if ( ! sendRequest( url ) ) { return false; } @@ -384,12 +397,21 @@ bool QgsWcsCapabilities::parseCapabilitiesDom( QByteArray const &xml, QgsWcsCapa tagName != "Capabilities" // 1.1, tags seen: Capabilities, wcs:Capabilities ) { - mErrorTitle = tr( "Dom Exception" ); - mErrorFormat = "text/plain"; - mError = tr( "Could not get WCS capabilities in the expected format (DTD): no %1 found.\nThis might be due to an incorrect WCS Server URL.\nTag:%3\nResponse was:\n%4" ) - .arg( "Capabilities" ) - .arg( docElem.tagName() ) - .arg( QString( xml ) ); + if ( tagName == "ExceptionReport" ) + { + mErrorTitle = tr( "Exception" ); + mErrorFormat = "text/plain"; + mError = tr( "Could not get WCS capabilities: %1" ).arg( domElementText( docElem, "Exception.ExceptionText" ) ); + } + else + { + mErrorTitle = tr( "Dom Exception" ); + mErrorFormat = "text/plain"; + mError = tr( "Could not get WCS capabilities in the expected format (DTD): no %1 found.\nThis might be due to an incorrect WCS Server URL.\nTag:%3\nResponse was:\n%4" ) + .arg( "Capabilities" ) + .arg( docElem.tagName() ) + .arg( QString( xml ) ); + } QgsLogger::debug( "Dom Exception: " + mError ); @@ -570,7 +592,7 @@ QList QgsWcsCapabilities::parseDoubles( const QString &text ) QString QgsWcsCapabilities::crsUrnToAuthId( const QString &text ) { - QString authid; + QString authid = text; // may be also non URN, for example 'EPSG:4326' // URN format: urn:ogc:def:objectType:authority:version:code // URN example: urn:ogc:def:crs:EPSG::4326 @@ -926,6 +948,25 @@ bool QgsWcsCapabilities::parseDescribeCoverageDom11( QByteArray const &xml, QgsW } } + QStringList formats = domElementsTexts( docElem, "CoverageDescription.SupportedFormat" ); + // There could be formats from GetCapabilities + if ( formats.size() > 0 ) + { + coverage->supportedFormat = formats; + } + + + QStringList crss = domElementsTexts( docElem, "CoverageDescription.SupportedCRS" ); + QSet authids; // Set, in case one CRS is in more formats (URN, non URN) + foreach ( QString crs, crss ) + { + authids.insert( crsUrnToAuthId( crs ) ); + } + if ( authids.size() > 0 ) + { + coverage->supportedCrs = authids.toList(); + } + coverage->described = true; return true; @@ -952,6 +993,7 @@ void QgsWcsCapabilities::parseCoverageSummary( QDomElement const & e, QgsWcsCove if ( tagName == "SupportedFormat" ) { // image/tiff, ... + // Formats may be here (UMN Mapserver) or may not (GeoServer) coverageSummary.supportedFormat << el.text(); } else if ( tagName == "SupportedCRS" ) diff --git a/src/providers/wcs/qgswcsprovider.cpp b/src/providers/wcs/qgswcsprovider.cpp index 27239a04da9e..d9d7981e7a2c 100644 --- a/src/providers/wcs/qgswcsprovider.cpp +++ b/src/providers/wcs/qgswcsprovider.cpp @@ -89,6 +89,10 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri ) , mErrors( 0 ) , mUserName( QString::null ) , mPassword( QString::null ) + , mFixBox( false ) + , mFixRotate( false ) + // TODO: optional + , mGetCoverageCacheLoadControl( QNetworkRequest::PreferCache ) { QgsDebugMsg( "constructing with uri '" + mHttpUri + "'." ); @@ -180,10 +184,6 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri ) mCachedMemFilename = QString( "/vsimem/qgis/wcs/%0.dat" ).arg(( qlonglong )this ); // Get small piece of coverage to find GDAL data type and nubmer of bands - // Using non native CRS (if we don't know which is native) it could easily happen, - // that a small part of bbox in request CRS near margin falls outside - // coverage native bbox and server reports error => take a piece from center - int bandNo = 0; // All bands int width; int height; @@ -219,12 +219,20 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri ) double yRes = box.height() / height; QgsPoint p = box.center(); - int half = 2; // to be requested + // width and height different to recognize rotation + int requestWidth = 6; + int requestHeight = 3; // extent to be used for test request - QgsRectangle extent = QgsRectangle( p.x() - half * xRes, p.y() - half * yRes, p.x() + half * xRes, p.y() + half * yRes ); + double halfWidth = xRes * ( requestWidth / 2. ); + double halfHeight = yRes * ( requestHeight / 2. ); - getCache( bandNo, extent, 2*half, 2*half, crs ); + // Using non native CRS (if we don't know which is native) it could easily happen, + // that a small part of bbox in request CRS near margin falls outside + // coverage native bbox and server reports error => take a piece from center + QgsRectangle extent = QgsRectangle( p.x() - halfWidth, p.y() - halfHeight, p.x() + halfWidth, p.y() + halfHeight ); + + getCache( bandNo, extent, requestWidth, requestHeight, crs ); if ( !mCachedGdalDataset ) { @@ -235,6 +243,28 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri ) mBandCount = GDALGetRasterCount( mCachedGdalDataset ); QgsDebugMsg( QString( "mBandCount = %1" ).arg( mBandCount ) ) ; + // Check for server particularities (bbox, rotation) + int responseWidth = GDALGetRasterXSize( mCachedGdalDataset ); + int responseHeight = GDALGetRasterYSize( mCachedGdalDataset ); + + QgsDebugMsg( QString( "requestWidth = %1 requestHeight = %2 responseWidth = %3 responseHeight = %4)" ).arg( requestWidth ).arg( requestHeight ).arg( responseWidth ).arg( responseHeight ) ); + // GeoServer and ArcGIS are using for 1.1 box "pixel" edges + // Mapserver is using pixel centers according to 1.1. specification + if (( responseWidth == requestWidth - 1 && responseHeight == requestHeight - 1 ) || + ( responseWidth == requestHeight - 1 && responseHeight == requestWidth - 1 ) ) + { + mFixBox = true; + QgsDebugMsg( "Test response size is smaller by pixel, using mFixBox" ); + } + // Geoserver is sometimes giving rotated raster (probably for geographic CRS, + // switched axis?) + if (( responseWidth == requestHeight && responseHeight == requestWidth ) || + ( responseWidth == requestHeight - 1 && responseHeight == requestWidth - 1 ) ) + { + mFixRotate = true; + QgsDebugMsg( "Test response is rotated, using mFixRotate" ); + } + // Get types // TODO: we are using the same data types like GDAL (not wider like GDAL provider) // with expectation to replace 'no data' values by NaN @@ -484,18 +514,40 @@ void QgsWcsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, in // TODO band int width = GDALGetRasterXSize( mCachedGdalDataset ); int height = GDALGetRasterYSize( mCachedGdalDataset ); - QgsDebugMsg( QString( "cached data width = %1 height = %2" ).arg( width ).arg( height ) ); + GDALRasterBandH gdalBand = GDALGetRasterBand( mCachedGdalDataset, bandNo ); + QgsDebugMsg( QString( "cached data width = %1 height = %2 (expected %3 x %4)" ).arg( width ).arg( height ).arg( pixelWidth ).arg( pixelHeight ) ); // TODO: check type? , check band count? - if ( width == pixelWidth && height == pixelHeight ) + if ( mFixRotate && width == pixelHeight && height == pixelWidth ) + { + // Rotate counter clockwise + // If GridOffsets With GeoServer, + QgsDebugMsg( tr( "Rotating raster" ) ); + int pixelSize = typeSize( dataType( bandNo ) ) / 8; + QgsDebugMsg( QString( "pixelSize = %1" ).arg( pixelSize ) ); + int size = width * height * pixelSize; + void * tmpData = malloc( size ); + GDALRasterIO( gdalBand, GF_Read, 0, 0, width, height, tmpData, width, height, ( GDALDataType ) mGdalDataType[bandNo-1], 0, 0 ); + for ( int i = 0; i < pixelHeight; i++ ) + { + for ( int j = 0; j < pixelWidth; j++ ) + { + int destIndex = pixelSize * ( i * pixelWidth + j ); + int srcIndex = pixelSize * ( j * width + ( width - i - 1 ) ); + memcpy(( char* )block + destIndex, ( char* )tmpData + srcIndex, pixelSize ); + } + } + free( tmpData ); + } + else if ( width == pixelWidth && height == pixelHeight ) { - GDALRasterBandH gdalBand = GDALGetRasterBand( mCachedGdalDataset, bandNo ); GDALRasterIO( gdalBand, GF_Read, 0, 0, pixelWidth, pixelHeight, block, pixelWidth, pixelHeight, ( GDALDataType ) mGdalDataType[bandNo-1], 0, 0 ); QgsDebugMsg( tr( "Block read OK" ) ); } else { - QgsMessageLog::logMessage( tr( "Received coverage has wrong size" ), tr( "WCS" ) ); - return; + // This should not happen, but it is better to give distorted result + warning + GDALRasterIO( gdalBand, GF_Read, 0, 0, width, height, block, pixelWidth, pixelHeight, ( GDALDataType ) mGdalDataType[bandNo-1], 0, 0 ); + QgsMessageLog::logMessage( tr( "Received coverage has wrong size %1 x %2 (expected %3 x %4)" ).arg( width ).arg( height ).arg( pixelWidth ).arg( pixelHeight ), tr( "WCS" ) ); } } } @@ -539,9 +591,16 @@ void QgsWcsProvider::getCache( int bandNo, QgsRectangle const & viewExtent, int double xRes = viewExtent.width() / pixelWidth; double yRes = viewExtent.height() / pixelHeight; QgsRectangle extent = viewExtent; - if ( mCapabilities.version().startsWith( "1.1" ) ) - { - // WCS 1.1 grid is using cell centers -> shrink the extent to border cells centers by half cell size + // WCS 1.1 grid is using grid points (surrounded by sample spaces) and + // "The spatial extent of a grid coverage extends only as far as the outermost + // grid points contained in the bounding box. It does NOT include any area + // (partial or whole grid cells or sample spaces) beyond those grid points." + // Mapserver and GDAL are using bbox defined by grid points, i.e. shrinked + // by 1 pixel, but Geoserver and ArcGIS are using full bbox including + // the space around edge grid points. + if ( mCapabilities.version().startsWith( "1.1" ) && !mFixBox ) + { + // shrink the extent to border cells centers by half cell size extent = QgsRectangle( viewExtent.xMinimum() + xRes / 2., viewExtent.yMinimum() + yRes / 2., viewExtent.xMaximum() - xRes / 2., viewExtent.yMaximum() - yRes / 2. ); } @@ -589,6 +648,7 @@ void QgsWcsProvider::getCache( int bandNo, QgsRectangle const & viewExtent, int setQueryItem( url, "BOUNDINGBOX", bbox ); + // Example: // GridBaseCRS=urn:ogc:def:crs:SG:6.6:32618 // GridType=urn:ogc:def:method:CS:1.1:2dGridIn2dCrs // GridCS=urn:ogc:def:cs:OGC:0Grid2dSquareCS @@ -596,20 +656,38 @@ void QgsWcsProvider::getCache( int bandNo, QgsRectangle const & viewExtent, int // GridOffsets=0.0707,-0.0707,0.1414,0.1414& setQueryItem( url, "GRIDBASECRS", crsUrn ); // response CRS + + setQueryItem( url, "GRIDCS", "urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS" ); + + setQueryItem( url, "GRIDTYPE", "urn:ogc:def:method:WCS:1.1:2dSimpleGrid" ); + // GridOrigin is BBOX minx, maxy - // TODO: invert axis, pixels centers + // Note: shifting origin to cell center (not realy necessary nor making sense) + // does not work with Mapserver 6.0.3 + // Mapserver 6.0.3 does not work with origin on yMinimum (lower left) + // Geoserver works OK with yMinimum (lower left) QString gridOrigin = QString( changeXY ? "%2,%1" : "%1,%2" ) .arg( extent.xMinimum(), 0, 'f', 16 ) .arg( extent.yMaximum(), 0, 'f', 16 ); setQueryItem( url, "GRIDORIGIN", gridOrigin ); + // GridOffsets WCS 1.1: + // GridType urn:ogc:def:method:WCS:1.1:2dSimpleGrid : 2 values + // GridType urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs : 4 values, the center two of these offsets will be zero when the GridCRS is not rotated or skewed in the GridBaseCRS. + // The second offset must be negative because origin is in upper corner + // WCS 1.1.2: BaseX = origin(1) + offsets(1) * GridX + // BaseY = origin(2) + offsets(2) * GridY + // otherwise GeoServer gives mirrored result, however + // Mapserver works OK with both positive and negative + double yOff = mFixRotate ? yRes : -yRes; QString gridOffsets = QString( changeXY ? "%2,%1" : "%1,%2" ) + //setQueryItem( url, "GRIDTYPE", "urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs" ); + //QString gridOffsets = QString( changeXY ? "%2,0,0,%1" : "%1,0,0,%2" ) .arg( xRes, 0, 'f', 16 ) - .arg( yRes, 0, 'f', 16 ); + .arg( yOff, 0, 'f', 16 ); setQueryItem( url, "GRIDOFFSETS", gridOffsets ); } - QgsDebugMsg( QString( "GetCoverage: %1" ).arg( url.toString() ) ); // cache some details for if the user wants to do an identifyAsHtml() later @@ -619,6 +697,7 @@ void QgsWcsProvider::getCache( int bandNo, QgsRectangle const & viewExtent, int QNetworkRequest request( url ); setAuthorization( request ); request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true ); + request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mGetCoverageCacheLoadControl ); mCacheReply = QgsNetworkAccessManager::instance()->get( request ); connect( mCacheReply, SIGNAL( finished() ), this, SLOT( cacheReplyFinished() ) ); connect( mCacheReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( cacheReplyProgress( qint64, qint64 ) ) ); @@ -762,7 +841,19 @@ void QgsWcsProvider::cacheReplyFinished() while ( true ) { to = data.indexOf( boundary.toAscii(), from ); - if ( to < 0 ) break; + if ( to < 0 ) + { + // It may happent that bondary is missing at the end (GeoServer) + // in that case, take everything to th end + if ( data.size() - from > 1 ) + { + to = data.size(); // out of range OK + } + else + { + break; + } + } QgsDebugMsg( QString( "part %1 - %2" ).arg( from ).arg( to ) ); QByteArray part = data.mid( from, to - from ); // Remove possible new line from beginning @@ -802,7 +893,61 @@ void QgsWcsProvider::cacheReplyFinished() // We will try the second one QgsMessageLog::logMessage( tr( "More than 2 parts (%1) received" ).arg( partBodies.size() ), tr( "WCS" ) ); } - mCachedData = partBodies.value( 1 ); + + QStringList headerRows = QString( partHeaders.value( 1 ) ).split( QRegExp( "[\n\r]+" ) ); + QString transferEncoding; + foreach ( QString row, headerRows ) + { + QgsDebugMsg( "row = " + row ); + //Content-Transfer-Encoding: base64 + QStringList kv = row.split( ": " ); + if ( kv.value( 0 ) == "Content-Transfer-Encoding" ) + { + transferEncoding = kv.value( 1 ); + break; + } + } + QgsDebugMsg( "transferEncoding = " + transferEncoding ); + + // It may happen (GeoServer) that in part header is for example + // Content-Type: image/tiff and Content-Transfer-Encoding: base64 + // but content is xml ExceptionReport which is not in base64 + if ( partBodies.value( 1 ).startsWith( "url().toString() ), tr( "WCS" ) ); + } + else + { + QgsMessageLog::logMessage( tr( "Map request error (Response: %2; URL:%3)" ) + .arg( QString::fromUtf8( partBodies.value( 1 ) ) ) + .arg( mCacheReply->url().toString() ), tr( "WCS" ) ); + } + + mCacheReply->deleteLater(); + mCacheReply = 0; + return; + } + + if ( transferEncoding == "binary" ) + { + mCachedData = partBodies.value( 1 ); + } + else if ( transferEncoding == "base64" ) + { + mCachedData = QByteArray::fromBase64( partBodies.value( 1 ) ); + } + else + { + QgsMessageLog::logMessage( tr( "Content-Transfer-Encoding %1 not supported" ).arg( transferEncoding ), tr( "WCS" ) ); + clearCache(); + mCacheReply->deleteLater(); + mCacheReply = 0; + return; + } } else { @@ -824,6 +969,16 @@ void QgsWcsProvider::cacheReplyFinished() } QgsDebugMsg( QString( "%1 bytes received" ).arg( mCachedData.size() ) ); +#if 1 + QFile myFile( "/tmp/qgiswcscache.dat" ); + if ( myFile.open( QIODevice::WriteOnly ) ) + { + QDataStream myStream( &myFile ); + myStream.writeRawData( mCachedData.data(), mCachedData.size() ); + myFile.close(); + } +#endif + mCachedMemFile = VSIFileFromMemBuffer( TO8F( mCachedMemFilename ), ( GByte* )mCachedData.data(), ( vsi_l_offset )mCachedData.size(), @@ -957,7 +1112,7 @@ QList QgsWcsProvider::colorTable( int theBand void QgsWcsProvider::cacheReplyProgress( qint64 bytesReceived, qint64 bytesTotal ) { QString msg = tr( "%1 of %2 bytes of map downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QString( "unknown number of" ) : QString::number( bytesTotal ) ); - QgsDebugMsg( msg ); + QgsDebugMsgLevel( msg, 3 ); emit statusChanged( msg ); } diff --git a/src/providers/wcs/qgswcsprovider.h b/src/providers/wcs/qgswcsprovider.h index c31682e381c1..08e520c6891e 100644 --- a/src/providers/wcs/qgswcsprovider.h +++ b/src/providers/wcs/qgswcsprovider.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -412,6 +413,13 @@ class QgsWcsProvider : public QgsRasterDataProvider, QgsGdalProviderBase QgsCoordinateReferenceSystem mCrs; + // Fix for servers using bbox 1 px bigger + bool mFixBox; + + // Fix for rasters rotated by GeoServer + bool mFixRotate; + + QNetworkRequest::CacheLoadControl mGetCoverageCacheLoadControl; }; diff --git a/tests/src/providers/testqgswcspublicservers.cpp b/tests/src/providers/testqgswcspublicservers.cpp index 584cd50b5b42..72c8ff0d8f3c 100644 --- a/tests/src/providers/testqgswcspublicservers.cpp +++ b/tests/src/providers/testqgswcspublicservers.cpp @@ -40,11 +40,13 @@ void TestQgsWcsPublicServers::init() // init QGIS's paths - true means that all path will be inited from prefix QgsDebugMsg( "Entered" ); + mMaxCoverages = 2; + mCacheDir = QDir( "./wcstestcache" ); if ( !mCacheDir.exists() ) { QDir myDir = QDir::root(); - if ( !myDir.mkpath ( mCacheDir.absolutePath() ) ) + if ( !myDir.mkpath( mCacheDir.absolutePath() ) ) { QgsDebugMsg( "Cannot create cache dir " + mCacheDir.absolutePath() ); QCoreApplication::exit( 1 ); @@ -65,43 +67,52 @@ void TestQgsWcsPublicServers::init() void TestQgsWcsPublicServers::test( ) { QStringList versions; - versions << "" << "1.0" << "1.1"; // empty for default - + // It may happen that server supports 1.1.1, but does not accept 1.1 (http://zeus.pin.unifi.it/gi-wcs/http) + versions << "" << "1.0.0" << "1.1.0"; // empty for default QStringList servers; servers << "http://argon.geogr.uni-jena.de:8080/geoserver/ows"; + servers << "http://demo.geonode.org/geoserver/wcs"; servers << "http://demo.mapserver.org/cgi-bin/wcs"; + servers << "http://demo.opengeo.org/geoserver/wcs"; servers << "http://geobrain.laits.gmu.edu/cgi-bin/gbwcs-dem"; servers << "http://geobrain.laits.gmu.edu/cgi-bin/ows8/wcseo"; servers << "http://geobrain.laits.gmu.edu/cgi-bin/wcs110"; servers << "http://geobrain.laits.gmu.edu/cgi-bin/wcs-all"; servers << "http://iceds.ge.ucl.ac.uk/cgi-bin/icedswcs"; servers << "http://motherlode.ucar.edu:8080/thredds/wcs/fmrc/NCEP/DGEX/Alaska_12km/NCEP-DGEX-Alaska_12km_best.ncd"; + servers << "http://navigator.state.or.us/ArcGIS/services/Framework/Imagery_Mosaic2009/ImageServer/WCSServer"; servers << "http://nsidc.org/cgi-bin/atlas_north"; servers << "http://sedac.ciesin.columbia.edu/geoserver/wcs"; - servers << "http://webmap.ornl.gov/ogcbroker/wcs"; + // Big and slow + //servers << "http://webmap.ornl.gov/ogcbroker/wcs"; servers << "http://ws.csiss.gmu.edu/cgi-bin/wcs-t"; - servers << "http://ws.laits.gmu.edu/cgi-bin/wcs-all"; - servers << "http://zeus.pin.unifi.it/gi-wcs/http"; + // Big and slow + //servers << "http://ws.laits.gmu.edu/cgi-bin/wcs-all"; + // Currently very slow or down + //servers << "http://www.sogeo.ch/geoserver/wcs"; + // Slow and erroneous + //servers << "http://zeus.pin.unifi.it/gi-wcs/http"; foreach ( QString server, servers ) { QStringList myServerLog; myServerLog << "server:" + server; QString myServerDirName = server; - myServerDirName.replace( QRegExp("[:/]+"), "." ); - myServerDirName.replace( QRegExp("\\.$"), "" ); + myServerDirName.replace( QRegExp( "[:/]+" ), "." ); + myServerDirName.replace( QRegExp( "\\.$" ), "" ); QgsDebugMsg( "myServerDirName = " + myServerDirName ); - QDir myServerDir ( mCacheDir.absolutePath() + QDir::separator() + myServerDirName ); + QDir myServerDir( mCacheDir.absolutePath() + QDir::separator() + myServerDirName ); QString myServerLogPath = myServerDir.absolutePath() + QDir::separator() + "server.log"; - if ( QFileInfo(myServerLogPath).exists() ) { - QgsDebugMsg( "cache exists " + myServerDir.absolutePath() ); + if ( QFileInfo( myServerLogPath ).exists() ) + { + QgsDebugMsg( "cache exists " + myServerDir.absolutePath() ); continue; } if ( !myServerDir.exists() ) { - mCacheDir.mkdir ( myServerDirName ); + mCacheDir.mkdir( myServerDirName ); } foreach ( QString version, versions ) @@ -122,7 +133,7 @@ void TestQgsWcsPublicServers::test( ) if ( !myCapabilities.lastError().isEmpty() ) { QgsDebugMsg( myCapabilities.lastError() ); - myServerLog << "error:" + myCapabilities.lastError(); + myServerLog << "error: (version: " + version + ") " + myCapabilities.lastError().replace( "\n", " " ); continue; } @@ -130,18 +141,30 @@ void TestQgsWcsPublicServers::test( ) if ( !myCapabilities.supportedCoverages( myCoverages ) ) { QgsDebugMsg( "Cannot get list of coverages" ); - myServerLog << "error:Cannot get list of coverages"; + myServerLog << "error: (version: " + version + ") Cannot get list of coverages"; continue; } + int myCoverageCount = 0; foreach ( QgsWcsCoverageSummary myCoverage, myCoverages ) { QgsDebugMsg( "coverage: " + myCoverage.identifier ); - QString myPath = myServerDir.absolutePath() + QDir::separator() + myCoverage.identifier; + myCoverageCount++; + if ( myCoverageCount > mMaxCoverages ) continue; + QString myPath = myServerDir.absolutePath() + QDir::separator() + myCoverage.identifier; + if ( !version.isEmpty() ) { myPath += "-" + version; } + QString myLogPath = myPath + ".log"; + + if ( QFileInfo( myLogPath ).exists() ) + { + QMap log = readLog( myLogPath ); + if ( !log.value( "identifier" ).isEmpty() && log.value( "error" ).isEmpty() ) continue; + } + QStringList myLog; myLog << "identifier:" + myCoverage.identifier; myCapabilities.describeCoverage( myCoverage.identifier ); @@ -150,7 +173,7 @@ void TestQgsWcsPublicServers::test( ) myUri.setParam( "identifier", myCoverage.identifier ); if ( myCoverage.times.size() > 0 ) { - myUri.setParam( "time", myCoverage.times.value(0) ); + myUri.setParam( "time", myCoverage.times.value( 0 ) ); } myLog << "version:" + version; myLog << "uri:" + myUri.encodedUri(); @@ -159,9 +182,9 @@ void TestQgsWcsPublicServers::test( ) int myHeight = 100; if ( myCoverage.hasSize ) { - myHeight = static_cast( qRound( 1.0 * myWidth * myCoverage.height / myCoverage.width ) ); + myHeight = static_cast( qRound( 1.0 * myWidth * myCoverage.height / myCoverage.width ) ); } - myLog << QString("hasSize:%1").arg( myCoverage.hasSize ); + myLog << QString( "hasSize:%1" ).arg( myCoverage.hasSize ); QgsRasterLayer * myLayer = new QgsRasterLayer( myUri.encodedUri(), myCoverage.identifier, "wcs", true ); if ( myLayer->isValid() ) @@ -170,7 +193,7 @@ void TestQgsWcsPublicServers::test( ) myLog << "bandCount:" + QString::number( myBandCount ); if ( myBandCount > 0 ) { - myLog << "srcType:" + QString::number( myLayer->dataProvider()->srcDataType(1) ); + myLog << "srcType:" + QString::number( myLayer->dataProvider()->srcDataType( 1 ) ); QgsRasterBandStats myStats = myLayer->dataProvider()->bandStatistics( 1, QgsRasterBandStats::All, QgsRectangle(), myWidth * myHeight ); myLog << "min:" + QString::number( myStats.minimumValue ); @@ -180,7 +203,7 @@ void TestQgsWcsPublicServers::test( ) QgsMapRenderer myMapRenderer; QList myLayersList; - myLayersList.append ( myLayer ); + myLayersList.append( myLayer ); QgsMapLayerRegistry::instance()->addMapLayers( myLayersList, false ); QMap myLayersMap = QgsMapLayerRegistry::instance()->mapLayers(); @@ -189,7 +212,6 @@ void TestQgsWcsPublicServers::test( ) myMapRenderer.setExtent( myLayer->extent() ); - QImage myImage( myWidth, myHeight, QImage::Format_ARGB32_Premultiplied ); myImage.fill( 0 ); @@ -213,15 +235,15 @@ void TestQgsWcsPublicServers::test( ) { for ( int col = 0; col < myWidth; col++ ) { - double value = myLayer->dataProvider()->readValue( myData, myType, row*myWidth + col ); - QString valueStr = QString::number(value); - if ( !myValues.contains ( valueStr ) ) myValues.insert( valueStr ); + double value = myLayer->dataProvider()->readValue( myData, myType, row * myWidth + col ); + QString valueStr = QString::number( value ); + if ( !myValues.contains( valueStr ) ) myValues.insert( valueStr ); } } - free(myData); + free( myData ); } - QgsDebugMsg( QString("%1 values").arg( myValues.size() ) ); - myLog << QString("valuesCount:%1").arg( myValues.size() ); + QgsDebugMsg( QString( "%1 values" ).arg( myValues.size() ) ); + myLog << QString( "valuesCount:%1" ).arg( myValues.size() ); // Verify image colors QSet myColors; @@ -229,33 +251,32 @@ void TestQgsWcsPublicServers::test( ) { for ( int col = 0; col < myWidth; col++ ) { - QRgb color = myImage.pixel ( col, row ); - if ( !myColors.contains ( color ) ) myColors.insert(color); + QRgb color = myImage.pixel( col, row ); + if ( !myColors.contains( color ) ) myColors.insert( color ); } } - QgsDebugMsg( QString("%1 colors").arg( myColors.size() ) ); - myLog << QString("colorsCount:%1").arg( myColors.size() ); + QgsDebugMsg( QString( "%1 colors" ).arg( myColors.size() ) ); + myLog << QString( "colorsCount:%1" ).arg( myColors.size() ); } else { - QgsDebugMsg( "Layer is not valid"); + QgsDebugMsg( "Layer is not valid" ); myLog << "error:Layer is not valid"; } - - QString myLogPath = myPath + ".log"; + QFile myLogFile( myLogPath ); - myLogFile.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream myStream(&myLogFile); - myStream << myLog.join("\n"); + myLogFile.open( QIODevice::WriteOnly | QIODevice::Text ); + QTextStream myStream( &myLogFile ); + myStream << myLog.join( "\n" ); myLogFile.close(); QgsMapLayerRegistry::instance()->removeAllMapLayers(); } } QFile myServerLogFile( myServerLogPath ); - myServerLogFile.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream myStream(&myServerLogFile); - myStream << myServerLog.join("\n"); + myServerLogFile.open( QIODevice::WriteOnly | QIODevice::Text ); + QTextStream myStream( &myServerLogFile ); + myStream << myServerLog.join( "\n" ); myServerLogFile.close(); } } @@ -270,7 +291,7 @@ void TestQgsWcsPublicServers::writeReport( QString theReport ) myStream << theReport; myFile.close(); } - QgsDebugMsg ( "Report written to " + myReportFile ); + QgsDebugMsg( "Report written to " + myReportFile ); } void TestQgsWcsPublicServers::report() @@ -289,25 +310,29 @@ void TestQgsWcsPublicServers::report() int myErrCount = 0; int myWarnCount = 0; myServerCount++; - QDir myDir ( mCacheDir.absolutePath() + QDir::separator() + myDirName ); + QDir myDir( mCacheDir.absolutePath() + QDir::separator() + myDirName ); QString myServerLogPath = myDir.absolutePath() + QDir::separator() + "server.log"; - QMap myServerLog = readLog ( myServerLogPath ); + QMap myServerLog = readLog( myServerLogPath ); - myReport += QString( "

%1

" ).arg( myServerLog.value("server") ); + myReport += QString( "

%1

" ).arg( myServerLog.value( "server" ) ); QString myServerReport; - if ( !myServerLog.value( "error").isEmpty() ) + if ( !myServerLog.value( "error" ).isEmpty() ) { - myServerReport += error( myServerLog.value( "error") ); - myServerErrCount++; + // Server may have more errors, for each version + foreach ( QString err, myServerLog.values( "error" ) ) + { + myServerReport += error( err ); + } + myServerErrCount++; } else { myServerReport += ""; - myServerReport += row(mHead); + myServerReport += row( mHead ); QStringList filters; filters << "*.log"; - myDir.setNameFilters(filters); + myDir.setNameFilters( filters ); foreach ( QString myLogFileName, myDir.entryList( QDir::Files ) ) { if ( myLogFileName == "server.log" ) continue; @@ -315,16 +340,16 @@ void TestQgsWcsPublicServers::report() myCoverageCount++; QString myLogPath = myDir.absolutePath() + QDir::separator() + myLogFileName; - QMapmyLog = readLog ( myLogPath ); + QMapmyLog = readLog( myLogPath ); QStringList myValues; myValues << myLog.value( "identifier" ); myValues << myLog.value( "version" ); - QString imgPath = myDirName + QDir::separator() + QFileInfo(myLogPath).completeBaseName() + ".png"; + QString imgPath = myDirName + QDir::separator() + QFileInfo( myLogPath ).completeBaseName() + ".png"; - if ( !myLog.value( "error").isEmpty() ) + if ( !myLog.value( "error" ).isEmpty() ) { - myValues << myLog.value( "error"); - myServerReport += row(myValues, "cellerr" ); + myValues << myLog.value( "error" ); + myServerReport += row( myValues, "cellerr" ); myErrCount++; myCoverageErrCount++; } @@ -339,10 +364,10 @@ void TestQgsWcsPublicServers::report() myValues << myLog.value( "colorsCount" ); myValues << myLog.value( "hasSize" ); - QString cls; + QString cls; int myValuesCount = myLog.value( "valuesCount" ).toInt(); int myColorsCount = myLog.value( "colorsCount" ).toInt(); - if ( myValuesCount < 4 ) + if ( myValuesCount < 4 ) { cls = "cellerr"; myErrCount++; @@ -355,15 +380,15 @@ void TestQgsWcsPublicServers::report() myCoverageWarnCount++; } - myServerReport += row(myValues, cls ); + myServerReport += row( myValues, cls ); } } myServerReport += "
"; // prepend counts - myServerReport.prepend ( QString( "Coverages: %1
" ).arg( myCount ) + - QString( "Errors: %1
" ).arg( myErrCount ) + - QString( "Warnings: %1

" ).arg( myWarnCount ) ); - } + myServerReport.prepend( QString( "Coverages: %1
" ).arg( myCount ) + + QString( "Errors: %1
" ).arg( myErrCount ) + + QString( "Warnings: %1

" ).arg( myWarnCount ) ); + } myReport += myServerReport; } @@ -382,6 +407,7 @@ void TestQgsWcsPublicServers::report() myRep += ".errmsg { color: #ff0000; }"; myRep += ""; + myRep += QString( "

Tested first %1 coverages for each server/version

" ).arg( mMaxCoverages ); myRep += QString( "Servers: %1
" ).arg( myServerCount ); myRep += QString( "Servers failed: %1
" ).arg( myServerErrCount ); myRep += QString( "Coverages: %1
" ).arg( myCoverageCount ); @@ -393,21 +419,22 @@ void TestQgsWcsPublicServers::report() writeReport( myRep ); } -QMap TestQgsWcsPublicServers::readLog ( QString theFileName ) +QMap TestQgsWcsPublicServers::readLog( QString theFileName ) { - QMap myMap; + QMap myMap; QFile myFile( theFileName ); if ( myFile.open( QIODevice::ReadOnly ) ) { QTextStream myStream( &myFile ); - foreach ( QString row, myStream.readAll().split("\n") ) + foreach ( QString row, myStream.readAll().split( "\n" ) ) { - int sepIdx = row.indexOf ( ":" ); - myMap.insert ( row.left(sepIdx), row.mid(sepIdx+1) ); + int sepIdx = row.indexOf( ":" ); + myMap.insert( row.left( sepIdx ), row.mid( sepIdx + 1 ) ); } + myFile.close(); } - return myMap; + return myMap; } QString TestQgsWcsPublicServers::error( QString theMessage ) @@ -423,11 +450,11 @@ QString TestQgsWcsPublicServers::row( QStringList theValues, QString theClass ) QString myRow = ""; for ( int i = 0; i < theValues.size(); i++ ) { - QString val = theValues.value(i); + QString val = theValues.value( i ); QString colspan; - if ( theValues.size() < mHead.size() && i == (theValues.size() - 1) ) + if ( theValues.size() < mHead.size() && i == ( theValues.size() - 1 ) ) { - colspan = QString("colspan=%1").arg ( mHead.size() - theValues.size() + 1 ) ; + colspan = QString( "colspan=%1" ).arg( mHead.size() - theValues.size() + 1 ) ; } myRow += QString( "%3" ).arg( theClass ).arg( colspan ).arg( val ); } @@ -437,7 +464,7 @@ QString TestQgsWcsPublicServers::row( QStringList theValues, QString theClass ) int main( int argc, char *argv[] ) { - QgsApplication myApp( argc, argv, false); + QgsApplication myApp( argc, argv, false ); QgsApplication::init( QString() ); QgsApplication::initQgis(); diff --git a/tests/src/providers/testqgswcspublicservers.h b/tests/src/providers/testqgswcspublicservers.h index a2d932caeaeb..38734a05cef9 100644 --- a/tests/src/providers/testqgswcspublicservers.h +++ b/tests/src/providers/testqgswcspublicservers.h @@ -24,9 +24,9 @@ #include #include -/** +/** * This class tries to get samples of coverages from public WCS servers, - * cache results and write report. + * cache results and write report. */ class TestQgsWcsPublicServers: public QObject { @@ -40,9 +40,11 @@ class TestQgsWcsPublicServers: public QObject QString error( QString theMessage ); void writeReport( QString theReport ); - QMap readLog ( QString theFileName ); + QMap readLog( QString theFileName ); QDir mCacheDir; QString mReport; QStringList mHead; + // Max coverages to test per server/version + int mMaxCoverages; };