277 changes: 248 additions & 29 deletions src/providers/wcs/qgswcsprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri )
: QgsRasterDataProvider( uri )
, mHttpUri( QString::null )
, mCoverageCrs( DEFAULT_LATLON_CRS )
, mCachedImage( 0 )
, mCacheReply( 0 )
, mCachedViewExtent( 0 )
, mCoordinateTransform( 0 )
Expand All @@ -85,6 +84,12 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri )
, mUserName( QString::null )
, mPassword( QString::null )
, mCoverageSummary( 0 )
, mCachedGdalDataset( 0 )
, mCachedMemFile( 0 )
, mWidth( 0 )
, mHeight( 0 )
, mHasSize( false )
, mBandCount( 0 )
{
QgsDebugMsg( "constructing with uri '" + mHttpUri + "'." );

Expand Down Expand Up @@ -119,15 +124,108 @@ QgsWcsProvider::QgsWcsProvider( QString const &uri )
QgsDebugMsg( "coverage not found" );
return;
}
mWidth = mCoverageSummary->width;
mHeight = mCoverageSummary->height;
mHasSize = mCoverageSummary->hasSize;

if ( !calculateExtent() )
{
QgsDebugMsg( "Cannot calculate extent" );
return;
}

mCachedMemFilename = QString( "/vsimem/qgis/wcs/%0.dat" ).arg(( qlonglong )this );

// Get small piece of coverage to find GDAL data type and nubmer of bands
int bandNo = 0; // All bands
double xRes, yRes;
int size = 5; // to be requested
int width = 1000; // just some number to get smaller piece of coverage
int height = 1000;
if ( mHasSize )
{
width = mWidth;
height = mHeight;
}
xRes = mCoverageExtent.width() / width;
yRes = mCoverageExtent.height() / height;
QgsRectangle extent = mCoverageExtent;
extent.setXMaximum( extent.xMinimum() + size * xRes );
extent.setYMaximum( extent.yMinimum() + size * yRes );
getCache( bandNo, extent, size, size );

if ( !mCachedGdalDataset )
{
QgsDebugMsg( "Cannot get test dataset." );
return;
}

mBandCount = GDALGetRasterCount( mCachedGdalDataset );
QgsDebugMsg( QString( "mBandCount = %1" ).arg( mBandCount ) ) ;

// 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
for ( int i = 1; i <= mBandCount; i++ )
{
GDALRasterBandH gdalBand = GDALGetRasterBand( mCachedGdalDataset, i );
GDALDataType myGdalDataType = GDALGetRasterDataType( gdalBand );

mSrcGdalDataType.append( myGdalDataType );
// TODO: This could be shared with GDAL provider
int isValid = false;
double myNoDataValue = GDALGetRasterNoDataValue( gdalBand, &isValid );
if ( isValid )
{
QgsDebugMsg( QString( "GDALGetRasterNoDataValue = %1" ).arg( myNoDataValue ) ) ;
mGdalDataType.append( myGdalDataType );
}
else
{
// But we need a null value in case of reprojection and BTW also for
// aligned margines
switch ( dataTypeFormGdal( myGdalDataType ) )
{

case QgsRasterDataProvider::Byte:
// Use longer data type to avoid conflict with real data
myNoDataValue = -32768.0;
mGdalDataType.append( GDT_Int16 );
break;
case QgsRasterDataProvider::Int16:
myNoDataValue = -2147483648.0;
mGdalDataType.append( GDT_Int32 );
break;
case QgsRasterDataProvider::UInt16:
myNoDataValue = -2147483648.0;
mGdalDataType.append( GDT_Int32 );
break;
case QgsRasterDataProvider::Int32:
myNoDataValue = -2147483648.0;
mGdalDataType.append( myGdalDataType );
break;
case QgsRasterDataProvider::UInt32:
myNoDataValue = 4294967295.0;
mGdalDataType.append( myGdalDataType );
break;
default:
myNoDataValue = std::numeric_limits<int>::max();
// Would NaN work well?
//myNoDataValue = std::numeric_limits<double>::quiet_NaN();
mGdalDataType.append( myGdalDataType );
}
}
mNoDataValue.append( myNoDataValue );

QgsDebugMsg( QString( "mSrcGdalDataType[%1] = %2" ).arg( i - 1 ).arg( mSrcGdalDataType[i-1] ) );
QgsDebugMsg( QString( "mGdalDataType[%1] = %2" ).arg( i - 1 ).arg( mGdalDataType[i-1] ) );
QgsDebugMsg( QString( "mNoDataValue[%1] = %2" ).arg( i - 1 ).arg( mNoDataValue[i-1] ) );
}

clearCache();

mValid = true;
QgsDebugMsg( "exiting constructor." );
QgsDebugMsg( "Constructed ok, provider valid." );
}

void QgsWcsProvider::parseUri( QString uriString )
Expand Down Expand Up @@ -181,11 +279,7 @@ QgsWcsProvider::~QgsWcsProvider()
QgsDebugMsg( "deconstructing." );

// Dispose of any cached image as created by draw()
if ( mCachedImage )
{
delete mCachedImage;
mCachedImage = 0;
}
clearCache();

if ( mCoordinateTransform )
{
Expand Down Expand Up @@ -263,20 +357,41 @@ void QgsWcsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, in
}

// Can we reuse the previously cached image?
if ( mCachedImage &&
if ( mCachedGdalDataset &&
mCachedViewExtent == viewExtent &&
mCachedViewWidth == pixelWidth &&
mCachedViewHeight == pixelHeight )
{
//return mCachedImage;
}

// delete cached image and create network request(s) to fill it
if ( mCachedImage )
getCache( bandNo, viewExtent, pixelWidth, pixelHeight );

if ( mCachedGdalDataset )
{
//delete mCachedImage;
//mCachedImage = 0;
// TODO band
int width = GDALGetRasterXSize( mCachedGdalDataset );
int height = GDALGetRasterYSize( mCachedGdalDataset );
QgsDebugMsg( QString( "cached data width = %1 height = %2" ).arg( width ).arg( height ) );
// TODO: check type? , check band count?
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
{
QgsDebugMsg( tr( "Recieved coverage has wrong size" ) );
return;
}
}
}

void QgsWcsProvider::getCache( int bandNo, QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight )
{
// delete cached data
clearCache();

// --------------- BOUNDING BOX --------------------------------
//according to the WCS spec for 1.3, some CRS have inverted axis
Expand Down Expand Up @@ -372,10 +487,8 @@ void QgsWcsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, in
{
QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, WCS_THRESHOLD );
}

mWaiting = false;
//memcpy( block, ptr, myExpectedSize );
//return mCachedImage;

}

void QgsWcsProvider::cacheReplyFinished()
Expand Down Expand Up @@ -411,18 +524,44 @@ void QgsWcsProvider::cacheReplyFinished()

QString contentType = mCacheReply->header( QNetworkRequest::ContentTypeHeader ).toString();
QgsDebugMsg( "contentType: " + contentType );
if ( contentType.startsWith( "image/", Qt::CaseInsensitive ) )
//if ( contentType.startsWith( "image/", Qt::CaseInsensitive ) )
// TODO: How to recognize easily coverage data from exception?
if ( !contentType.startsWith( "text/", Qt::CaseInsensitive ) &&
contentType.toLower() != "application/vnd.ogc.se_xml" )
{
QImage myLocalImage = QImage::fromData( mCacheReply->readAll() );
if ( !myLocalImage.isNull() )
// Create memory file
clearCache();
mCachedData = mCacheReply->readAll();
if ( mCachedData.size() == 0 )
{
QPainter p( mCachedImage );
p.drawImage( 0, 0, myLocalImage );
QgsMessageLog::logMessage( tr( "No data recieved" ), tr( "WCS" ) );
clearCache();
return;
}
else
QgsDebugMsg( QString( "%1 bytes recieved" ).arg( mCachedData.size() ) );

mCachedMemFile = VSIFileFromMemBuffer( TO8F( mCachedMemFilename ),
( GByte* )mCachedData.data(),
( vsi_l_offset )mCachedData.size(),
FALSE );

if ( !mCachedMemFile )
{
QgsMessageLog::logMessage( tr( "Cannot create memory file" ), tr( "WCS" ) );
clearCache();
return;
}
QgsDebugMsg( "Memory file created" );

CPLErrorReset();
mCachedGdalDataset = GDALOpen( TO8F( mCachedMemFilename ), GA_ReadOnly );
if ( !mCachedGdalDataset )
{
QgsMessageLog::logMessage( tr( "Returned image is flawed [%1]" ).arg( mCacheReply->url().toString() ), tr( "WCS" ) );
QgsMessageLog::logMessage( QString::fromUtf8( CPLGetLastErrorMsg() ), tr( "WCS" ) );
clearCache();
return;
}
QgsDebugMsg( "Dataset opened" );
}
else
{
Expand Down Expand Up @@ -473,20 +612,93 @@ void QgsWcsProvider::cacheReplyFinished()
}
}

// This could be shared with GDAL provider
int QgsWcsProvider::srcDataType( int bandNo ) const
{
if ( bandNo < 0 || bandNo > mSrcGdalDataType.size() )
{
return QgsRasterDataProvider::UnknownDataType;
}

return dataTypeFormGdal( mSrcGdalDataType[bandNo-1] );
}

int QgsWcsProvider::dataType( int bandNo ) const
{
return srcDataType( bandNo );
if ( bandNo < 0 || bandNo > mGdalDataType.size() )
{
return QgsRasterDataProvider::UnknownDataType;
}

return dataTypeFormGdal( mGdalDataType[bandNo-1] );
}

int QgsWcsProvider::srcDataType( int bandNo ) const
int QgsWcsProvider::dataTypeFormGdal( int theGdalDataType ) const
{
Q_UNUSED( bandNo );
return QgsRasterDataProvider::ARGBDataType;
switch ( theGdalDataType )
{
case GDT_Unknown:
return QgsRasterDataProvider::UnknownDataType;
break;
case GDT_Byte:
return QgsRasterDataProvider::Byte;
break;
case GDT_UInt16:
return QgsRasterDataProvider::UInt16;
break;
case GDT_Int16:
return QgsRasterDataProvider::Int16;
break;
case GDT_UInt32:
return QgsRasterDataProvider::UInt32;
break;
case GDT_Int32:
return QgsRasterDataProvider::Int32;
break;
case GDT_Float32:
return QgsRasterDataProvider::Float32;
break;
case GDT_Float64:
return QgsRasterDataProvider::Float64;
break;
case GDT_CInt16:
return QgsRasterDataProvider::CInt16;
break;
case GDT_CInt32:
return QgsRasterDataProvider::CInt32;
break;
case GDT_CFloat32:
return QgsRasterDataProvider::CFloat32;
break;
case GDT_CFloat64:
return QgsRasterDataProvider::CFloat64;
break;
case GDT_TypeCount:
// make gcc happy
break;
}
return QgsRasterDataProvider::UnknownDataType;
}

int QgsWcsProvider::bandCount() const
{
return 1;
return mBandCount;
}

void QgsWcsProvider::clearCache()
{
QgsDebugMsg( "Entered" );
if ( mCachedGdalDataset )
{
GDALClose( mCachedGdalDataset );
mCachedGdalDataset = 0;
}
if ( mCachedMemFile )
{
VSIFCloseL( mCachedMemFile );
mCachedMemFile = 0;
}
mCachedData.clear();
}

void QgsWcsProvider::cacheReplyProgress( qint64 bytesReceived, qint64 bytesTotal )
Expand Down Expand Up @@ -721,6 +933,12 @@ int QgsWcsProvider::capabilities() const
int capability = NoCapabilities;
capability |= QgsRasterDataProvider::Identify;

if ( mHasSize )
{
capability |= QgsRasterDataProvider::ExactResolution;
capability |= QgsRasterDataProvider::Size;
}

return capability;
}
//TODO
Expand Down Expand Up @@ -1124,8 +1342,9 @@ QString QgsWcsProvider::description() const

void QgsWcsProvider::reloadData()
{
delete mCachedImage;
mCachedImage = 0;
//delete mCachedImage;
//mCachedImage = 0;
clearCache();
}

void QgsWcsProvider::setAuthorization( QNetworkRequest &request ) const
Expand Down
119 changes: 72 additions & 47 deletions src/providers/wcs/qgswcsprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ class QNetworkAccessManager;
class QNetworkReply;
class QNetworkRequest;

#define CPL_SUPRESS_CPLUSPLUS
#include <gdal.h>

#if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1800
#define TO8F(x) (x).toUtf8().constData()
#define FROM8(x) QString::fromUtf8(x)
#else
#define TO8F(x) QFile::encodeName( x ).constData()
#define FROM8(x) QString::fromLocal8Bit(x)
#endif

/**
\brief Data provider for OGC WCS layers.
Expand Down Expand Up @@ -107,6 +118,9 @@ class QgsWcsProvider : public QgsRasterDataProvider

void readBlock( int bandNo, QgsRectangle const & viewExtent, int width, int height, void *data );

/** Download cache */
void getCache( int bandNo, QgsRectangle const & viewExtent, int width, int height );

/** Return the extent for this data layer
*/
virtual QgsRectangle extent();
Expand Down Expand Up @@ -211,6 +225,12 @@ class QgsWcsProvider : public QgsRasterDataProvider
//! set authorization header
void setAuthorization( QNetworkRequest &request ) const;

//! Convert data type from GDAL to QGIS
int dataTypeFormGdal( int theGdalDataType ) const;

//! Release cache resources
void clearCache();

//! Data source URI of the WCS for this layer
QString mHttpUri;

Expand All @@ -234,16 +254,38 @@ class QgsWcsProvider : public QgsRasterDataProvider
/** Coverage summary */
QgsWcsCoverageSummary * mCoverageSummary;

/**
* Spatial reference id of the layer
*/
/** Spatial reference id of the layer */
QString mSrid;

/**
* Rectangle that contains the extent (bounding box) of the layer
*/
/** Rectangle that contains the extent (bounding box) of the layer */
QgsRectangle mCoverageExtent;

/** Coverage width, may be 0 if it could not be found in DescribeCoverage */
int mWidth;

/** Coverage width, may be 0 if it could not be found in DescribeCoverage */
int mHeight;

/** Flag if size was parsed successfully */
bool mHasSize;

/** Number of bands */
int mBandCount;

/** \brief Gdal data types used to represent data in in QGIS,
may be longer than source data type to keep nulls
indexed from 0 */
QList<int>mGdalDataType;

/** GDAL source data types, indexed from 0 */
QList<int>mSrcGdalDataType;

/** \brief Cell value representing no data. e.g. -9999, indexed from 0 */
QList<double> mNoDataValue;

/** Source data types */
//QVector<QgsRasterDataProvider::DataType> mDataTypes;

/**
* Last Service Exception Report from the WCS
*/
Expand All @@ -265,69 +307,51 @@ class QgsWcsProvider : public QgsRasterDataProvider
*/
QMap<QString, bool> mQueryableForLayer;

/**
* WCS CRS type of the coverage CRS requested from the WCS server
*/
/** WCS CRS type of the coverage CRS requested from the WCS server */
QString mCoverageCrs;

/**
* The previously retrieved image from the WCS server.
* This can be reused if draw() is called consecutively
* with the same parameters.
*/
QImage *mCachedImage;

/**
* The reply to the on going request to fill the cache
*/
/** The reply to the on going request to fill the cache */
QNetworkReply *mCacheReply;

/**
* Running tile requests
*/
QList<QNetworkReply*> mTileReplies;
/** Cached data */
QByteArray mCachedData;

/**
* The reply to the capabilities request
*/
QNetworkReply *mCapabilitiesReply;
/** Name of memory file for cached data */
QString mCachedMemFilename;

/**
* The reply to the capabilities request
*/
QNetworkReply *mIdentifyReply;
VSILFILE * mCachedMemFile;

/**
* The result of the identify reply
*/
QString mIdentifyResult;
/** Pointer to cached GDAL dataset */
GDALDatasetH mCachedGdalDataset;

/** \brief Values for mapping pixel to world coordinates. Contents of this array are the same as the GDAL adfGeoTransform */
//double mGeoTransform[6];

/**
* The previous parameters to draw().
* The previously retrieved image from the WCS server.
* This can be reused if draw() is called consecutively
* with the same parameters.
*/
//QImage *mCachedImage;


/** The previous parameters to draw(). */
QgsRectangle mCachedViewExtent;
int mCachedViewWidth;
int mCachedViewHeight;

/**
* Maximum width and height of getmap requests
*/
/** Maximum width and height of getmap requests */
int mMaxWidth;
int mMaxHeight;

/**
* The error caption associated with the last WCS error.
*/
/** The error caption associated with the last WCS error. */
QString mErrorCaption;

/**
* The error message associated with the last WCS error.
*/
/** The error message associated with the last WCS error. */
QString mError;


/** The mime type of the message
*/
/** The mime type of the message */
QString mErrorFormat;

//! A QgsCoordinateTransform is used for transformation of WCS layer extents
Expand Down Expand Up @@ -365,6 +389,7 @@ class QgsWcsProvider : public QgsRasterDataProvider
bool mInvertAxisOrientation;

QgsCoordinateReferenceSystem mCrs;

};


Expand Down