197 changes: 176 additions & 21 deletions src/providers/wcs/qgswcsprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 + "'." );

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 )
{
Expand All @@ -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
Expand Down Expand Up @@ -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" ) );
}
}
}
Expand Down Expand Up @@ -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. );
}

Expand Down Expand Up @@ -589,27 +648,46 @@ 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
// GridOrigin=0,0
// 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
Expand All @@ -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 ) ) );
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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( "<?xml" ) )
{
if ( parseServiceExceptionReportDom( partBodies.value( 1 ) ) )
{
QgsMessageLog::logMessage( tr( "Map request error (Title:%1; Error:%2; URL: %3)" )
.arg( mErrorCaption ).arg( mError )
.arg( mCacheReply->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
{
Expand All @@ -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(),
Expand Down Expand Up @@ -957,7 +1112,7 @@ QList<QgsColorRampShader::ColorRampItem> 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 );
}

Expand Down
8 changes: 8 additions & 0 deletions src/providers/wcs/qgswcsprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <QDomElement>
#include <QHash>
#include <QMap>
#include <QNetworkRequest>
#include <QVector>
#include <QUrl>

Expand Down Expand Up @@ -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;
};


Expand Down
169 changes: 98 additions & 71 deletions tests/src/providers/testqgswcspublicservers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -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 )
Expand All @@ -122,26 +133,38 @@ void TestQgsWcsPublicServers::test( )
if ( !myCapabilities.lastError().isEmpty() )
{
QgsDebugMsg( myCapabilities.lastError() );
myServerLog << "error:" + myCapabilities.lastError();
myServerLog << "error: (version: " + version + ") " + myCapabilities.lastError().replace( "\n", " " );
continue;
}

QVector<QgsWcsCoverageSummary> myCoverages;
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<QString, QString> log = readLog( myLogPath );
if ( !log.value( "identifier" ).isEmpty() && log.value( "error" ).isEmpty() ) continue;
}

QStringList myLog;
myLog << "identifier:" + myCoverage.identifier;
myCapabilities.describeCoverage( myCoverage.identifier );
Expand All @@ -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();
Expand All @@ -159,9 +182,9 @@ void TestQgsWcsPublicServers::test( )
int myHeight = 100;
if ( myCoverage.hasSize )
{
myHeight = static_cast<int>( qRound( 1.0 * myWidth * myCoverage.height / myCoverage.width ) );
myHeight = static_cast<int>( 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() )
Expand All @@ -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 );
Expand All @@ -180,7 +203,7 @@ void TestQgsWcsPublicServers::test( )
QgsMapRenderer myMapRenderer;
QList<QgsMapLayer *> myLayersList;

myLayersList.append ( myLayer );
myLayersList.append( myLayer );
QgsMapLayerRegistry::instance()->addMapLayers( myLayersList, false );

QMap<QString, QgsMapLayer*> myLayersMap = QgsMapLayerRegistry::instance()->mapLayers();
Expand All @@ -189,7 +212,6 @@ void TestQgsWcsPublicServers::test( )

myMapRenderer.setExtent( myLayer->extent() );


QImage myImage( myWidth, myHeight, QImage::Format_ARGB32_Premultiplied );
myImage.fill( 0 );

Expand All @@ -213,49 +235,48 @@ 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<QRgb> myColors;
for ( int row = 0; row < myHeight; row++ )
{
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();
}
}
Expand All @@ -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()
Expand All @@ -289,42 +310,46 @@ 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<QString,QString> myServerLog = readLog ( myServerLogPath );
QMap<QString, QString> myServerLog = readLog( myServerLogPath );

myReport += QString( "<h2>%1</h2>" ).arg( myServerLog.value("server") );
myReport += QString( "<h2>%1</h2>" ).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 += "<table class='tab'>";
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;
myCount++;
myCoverageCount++;

QString myLogPath = myDir.absolutePath() + QDir::separator() + myLogFileName;
QMap<QString,QString>myLog = readLog ( myLogPath );
QMap<QString, QString>myLog = 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++;
}
Expand All @@ -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++;
Expand All @@ -355,15 +380,15 @@ void TestQgsWcsPublicServers::report()
myCoverageWarnCount++;
}

myServerReport += row(myValues, cls );
myServerReport += row( myValues, cls );
}
}
myServerReport += "</table>";
// prepend counts
myServerReport.prepend ( QString( "<b>Coverages: %1</b><br>" ).arg( myCount ) +
QString( "<b>Errors: %1</b><br>" ).arg( myErrCount ) +
QString( "<b>Warnings: %1</b><br><br>" ).arg( myWarnCount ) );
}
myServerReport.prepend( QString( "<b>Coverages: %1</b><br>" ).arg( myCount ) +
QString( "<b>Errors: %1</b><br>" ).arg( myErrCount ) +
QString( "<b>Warnings: %1</b><br><br>" ).arg( myWarnCount ) );
}
myReport += myServerReport;
}

Expand All @@ -382,6 +407,7 @@ void TestQgsWcsPublicServers::report()
myRep += ".errmsg { color: #ff0000; }";
myRep += "</style>";

myRep += QString( "<p>Tested first %1 coverages for each server/version</p>" ).arg( mMaxCoverages );
myRep += QString( "<b>Servers: %1</b><br>" ).arg( myServerCount );
myRep += QString( "<b>Servers failed: %1</b><br>" ).arg( myServerErrCount );
myRep += QString( "<b>Coverages: %1</b><br>" ).arg( myCoverageCount );
Expand All @@ -393,21 +419,22 @@ void TestQgsWcsPublicServers::report()
writeReport( myRep );
}

QMap<QString,QString> TestQgsWcsPublicServers::readLog ( QString theFileName )
QMap<QString, QString> TestQgsWcsPublicServers::readLog( QString theFileName )
{
QMap<QString,QString> myMap;
QMap<QString, QString> 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 )
Expand All @@ -423,11 +450,11 @@ QString TestQgsWcsPublicServers::row( QStringList theValues, QString theClass )
QString myRow = "<tr>";
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( "<td class='cell %1' %2>%3</td>" ).arg( theClass ).arg( colspan ).arg( val );
}
Expand All @@ -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();

Expand Down
8 changes: 5 additions & 3 deletions tests/src/providers/testqgswcspublicservers.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
#include <qgsrasterdataprovider.h>
#include <qgsrasterlayer.h>

/**
/**
* 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
{
Expand All @@ -40,9 +40,11 @@ class TestQgsWcsPublicServers: public QObject
QString error( QString theMessage );
void writeReport( QString theReport );

QMap<QString,QString> readLog ( QString theFileName );
QMap<QString, QString> readLog( QString theFileName );

QDir mCacheDir;
QString mReport;
QStringList mHead;
// Max coverages to test per server/version
int mMaxCoverages;
};