QGIS/src/core/raster/qgsrasterdataprovider.cpp
Samweli 6352d6889c added abstract temporal class
updates on tests and abstract temporal class

updates on qgsabstracttemporal sip and tests

update qgsabstracttemporal test

updated docs

moved qgsabstracttemporal class to qgstemporalrangeobject class as per review suggestions

edits per review on qgs map canvas

remove access of private members from qgstemporalrangeobject

updated render context test and fix travis tests

added new temporal property class

added tests for temporal property

added tests for temporal property

added temporal widget ui

temporal support in raster layers, from wms provider

temporal properties in raster layer properties

added abstract temporal class

updates on tests and abstract temporal class

updates on qgsabstracttemporal sip and tests

update qgsabstracttemporal test

updated docs

temporal support in raster layers, from wms provider

update qgsabstracttemporal test

updated docs

moved qgsabstracttemporal class to qgstemporalrangeobject class as per review suggestions

edits per review on qgs map canvas

updated render context test and fix travis tests

added new temporal property class

added new map layer temporal properties sub classes with tests

updates on map layers temporal properties classes

fix travis tests

add read and write xml tests

added abstract temporal class

updates on tests and abstract temporal class

updates on qgsabstracttemporal sip and tests

update qgsabstracttemporal test

updated docs

edits per review on qgs map canvas

temporal properties in raster layer properties

temporal support in raster layers, from wms provider

moved qgsabstracttemporal class to qgstemporalrangeobject class as per review suggestions

edits per review on qgs map canvas

remove access of private members from qgstemporalrangeobject

updated render context test and fix travis tests

added new temporal property class

added new map layer temporal properties sub classes with tests

updated layer times ui

added advanced options in layer time constrains ui

fix for refreshing wms layer after temporal properties update

update on wms-t temporal properties

added support for bi-temporal WMS-T
2020-03-05 18:41:35 +10:00

528 lines
18 KiB
C++

/***************************************************************************
qgsrasterdataprovider.cpp - DataProvider Interface for raster layers
--------------------------------------
Date : Mar 11, 2005
Copyright : (C) 2005 by Brendan Morley
email : morb at ozemail dot com dot au
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsproviderregistry.h"
#include "qgsrasterdataprovider.h"
#include "qgsrasteridentifyresult.h"
#include "qgsprovidermetadata.h"
#include "qgsrasterprojector.h"
#include "qgslogger.h"
#include "qgsapplication.h"
#include <QTime>
#include <QMap>
#include <QByteArray>
#include <QVariant>
#define ERR(message) QgsError(message, "Raster provider")
void QgsRasterDataProvider::setUseSourceNoDataValue( int bandNo, bool use )
{
if ( mUseSrcNoDataValue.size() < bandNo )
{
for ( int i = mUseSrcNoDataValue.size(); i < bandNo; i++ )
{
mUseSrcNoDataValue.append( false );
}
}
mUseSrcNoDataValue[bandNo - 1] = use;
}
QgsRasterBlock *QgsRasterDataProvider::block( int bandNo, QgsRectangle const &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback )
{
QgsDebugMsgLevel( QStringLiteral( "bandNo = %1 width = %2 height = %3" ).arg( bandNo ).arg( width ).arg( height ), 4 );
QgsDebugMsgLevel( QStringLiteral( "boundingBox = %1" ).arg( boundingBox.toString() ), 4 );
std::unique_ptr< QgsRasterBlock > block = qgis::make_unique< QgsRasterBlock >( dataType( bandNo ), width, height );
if ( sourceHasNoDataValue( bandNo ) && useSourceNoDataValue( bandNo ) )
{
block->setNoDataValue( sourceNoDataValue( bandNo ) );
}
if ( block->isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Couldn't create raster block" ) );
return block.release();
}
// Read necessary extent only
QgsRectangle tmpExtent = boundingBox;
if ( tmpExtent.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Extent outside provider extent" ) );
block->setIsNoData();
return block.release();
}
double xRes = boundingBox.width() / width;
double yRes = boundingBox.height() / height;
double tmpXRes, tmpYRes;
double providerXRes = 0;
double providerYRes = 0;
if ( capabilities() & Size )
{
providerXRes = extent().width() / xSize();
providerYRes = extent().height() / ySize();
tmpXRes = std::max( providerXRes, xRes );
tmpYRes = std::max( providerYRes, yRes );
if ( qgsDoubleNear( tmpXRes, xRes ) ) tmpXRes = xRes;
if ( qgsDoubleNear( tmpYRes, yRes ) ) tmpYRes = yRes;
}
else
{
tmpXRes = xRes;
tmpYRes = yRes;
}
if ( tmpExtent != boundingBox ||
tmpXRes > xRes || tmpYRes > yRes )
{
// Read smaller extent or lower resolution
if ( !extent().contains( boundingBox ) )
{
QRect subRect = QgsRasterBlock::subRect( boundingBox, width, height, extent() );
block->setIsNoDataExcept( subRect );
}
// Calculate row/col limits (before tmpExtent is aligned)
int fromRow = std::round( ( boundingBox.yMaximum() - tmpExtent.yMaximum() ) / yRes );
int toRow = std::round( ( boundingBox.yMaximum() - tmpExtent.yMinimum() ) / yRes ) - 1;
int fromCol = std::round( ( tmpExtent.xMinimum() - boundingBox.xMinimum() ) / xRes );
int toCol = std::round( ( tmpExtent.xMaximum() - boundingBox.xMinimum() ) / xRes ) - 1;
QgsDebugMsgLevel( QStringLiteral( "fromRow = %1 toRow = %2 fromCol = %3 toCol = %4" ).arg( fromRow ).arg( toRow ).arg( fromCol ).arg( toCol ), 4 );
if ( fromRow < 0 || fromRow >= height || toRow < 0 || toRow >= height ||
fromCol < 0 || fromCol >= width || toCol < 0 || toCol >= width )
{
// Should not happen
QgsDebugMsg( QStringLiteral( "Row or column limits out of range" ) );
return block.release();
}
// If lower source resolution is used, the extent must beS aligned to original
// resolution to avoid possible shift due to resampling
if ( tmpXRes > xRes )
{
int col = std::floor( ( tmpExtent.xMinimum() - extent().xMinimum() ) / providerXRes );
tmpExtent.setXMinimum( extent().xMinimum() + col * providerXRes );
col = std::ceil( ( tmpExtent.xMaximum() - extent().xMinimum() ) / providerXRes );
tmpExtent.setXMaximum( extent().xMinimum() + col * providerXRes );
}
if ( tmpYRes > yRes )
{
int row = std::floor( ( extent().yMaximum() - tmpExtent.yMaximum() ) / providerYRes );
tmpExtent.setYMaximum( extent().yMaximum() - row * providerYRes );
row = std::ceil( ( extent().yMaximum() - tmpExtent.yMinimum() ) / providerYRes );
tmpExtent.setYMinimum( extent().yMaximum() - row * providerYRes );
}
int tmpWidth = std::round( tmpExtent.width() / tmpXRes );
int tmpHeight = std::round( tmpExtent.height() / tmpYRes );
tmpXRes = tmpExtent.width() / tmpWidth;
tmpYRes = tmpExtent.height() / tmpHeight;
QgsDebugMsgLevel( QStringLiteral( "Reading smaller block tmpWidth = %1 height = %2" ).arg( tmpWidth ).arg( tmpHeight ), 4 );
QgsDebugMsgLevel( QStringLiteral( "tmpExtent = %1" ).arg( tmpExtent.toString() ), 4 );
std::unique_ptr< QgsRasterBlock > tmpBlock = qgis::make_unique< QgsRasterBlock >( dataType( bandNo ), tmpWidth, tmpHeight );
if ( sourceHasNoDataValue( bandNo ) && useSourceNoDataValue( bandNo ) )
{
tmpBlock->setNoDataValue( sourceNoDataValue( bandNo ) );
}
if ( !readBlock( bandNo, tmpExtent, tmpWidth, tmpHeight, tmpBlock->bits(), feedback ) )
{
QgsDebugMsg( QStringLiteral( "Error occurred while reading block" ) );
block->setIsNoData();
return block.release();
}
int pixelSize = dataTypeSize( bandNo );
double xMin = boundingBox.xMinimum();
double yMax = boundingBox.yMaximum();
double tmpXMin = tmpExtent.xMinimum();
double tmpYMax = tmpExtent.yMaximum();
for ( int row = fromRow; row <= toRow; row++ )
{
double y = yMax - ( row + 0.5 ) * yRes;
int tmpRow = std::floor( ( tmpYMax - y ) / tmpYRes );
for ( int col = fromCol; col <= toCol; col++ )
{
double x = xMin + ( col + 0.5 ) * xRes;
int tmpCol = std::floor( ( x - tmpXMin ) / tmpXRes );
if ( tmpRow < 0 || tmpRow >= tmpHeight || tmpCol < 0 || tmpCol >= tmpWidth )
{
QgsDebugMsg( QStringLiteral( "Source row or column limits out of range" ) );
block->setIsNoData(); // so that the problem becomes obvious and fixed
return block.release();
}
qgssize tmpIndex = static_cast< qgssize >( tmpRow ) * static_cast< qgssize >( tmpWidth ) + tmpCol;
qgssize index = row * static_cast< qgssize >( width ) + col;
char *tmpBits = tmpBlock->bits( tmpIndex );
char *bits = block->bits( index );
if ( !tmpBits )
{
QgsDebugMsg( QStringLiteral( "Cannot get input block data tmpRow = %1 tmpCol = %2 tmpIndex = %3." ).arg( tmpRow ).arg( tmpCol ).arg( tmpIndex ) );
continue;
}
if ( !bits )
{
QgsDebugMsg( QStringLiteral( "Cannot set output block data." ) );
continue;
}
memcpy( bits, tmpBits, pixelSize );
}
}
}
else
{
if ( !readBlock( bandNo, boundingBox, width, height, block->bits(), feedback ) )
{
QgsDebugMsg( QStringLiteral( "Error occurred while reading block" ) );
block->setIsNoData();
return block.release();
}
}
// apply scale and offset
block->applyScaleOffset( bandScale( bandNo ), bandOffset( bandNo ) );
// apply user no data values
block->applyNoDataValues( userNoDataValues( bandNo ) );
return block.release();
}
QgsRasterDataProvider::QgsRasterDataProvider()
: QgsDataProvider( QString(), QgsDataProvider::ProviderOptions() )
, QgsRasterInterface( nullptr )
{
mTemporalProperties = std::unique_ptr< QgsRasterDataProviderTemporalProperties >( new QgsRasterDataProviderTemporalProperties() );
}
QgsRasterDataProvider::QgsRasterDataProvider( const QString &uri, const ProviderOptions &options )
: QgsDataProvider( uri, options )
, QgsRasterInterface( nullptr )
{
mTemporalProperties = std::unique_ptr< QgsRasterDataProviderTemporalProperties >( new QgsRasterDataProviderTemporalProperties() );
}
QgsRasterDataProvider::ProviderCapabilities QgsRasterDataProvider::providerCapabilities() const
{
return QgsRasterDataProvider::NoProviderCapabilities;
}
//
//Random Static convenience function
//
/////////////////////////////////////////////////////////
// convenience function for building metadata() HTML table cells
QString QgsRasterDataProvider::htmlMetadata()
{
QString s;
return s;
}
// Default implementation for values
QgsRasterIdentifyResult QgsRasterDataProvider::identify( const QgsPointXY &point, QgsRaster::IdentifyFormat format, const QgsRectangle &boundingBox, int width, int height, int /*dpi*/ )
{
QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
QMap<int, QVariant> results;
if ( format != QgsRaster::IdentifyFormatValue || !( capabilities() & IdentifyValue ) )
{
QgsDebugMsg( QStringLiteral( "Format not supported" ) );
return QgsRasterIdentifyResult( ERR( tr( "Format not supported" ) ) );
}
if ( !extent().contains( point ) )
{
// Outside the raster
for ( int bandNo = 1; bandNo <= bandCount(); bandNo++ )
{
results.insert( bandNo, QVariant() );
}
return QgsRasterIdentifyResult( QgsRaster::IdentifyFormatValue, results );
}
QgsRectangle finalExtent = boundingBox;
if ( finalExtent.isEmpty() )
finalExtent = extent();
if ( width == 0 )
{
width = capabilities() & Size ? xSize() : 1000;
}
if ( height == 0 )
{
height = capabilities() & Size ? ySize() : 1000;
}
// Calculate the row / column where the point falls
double xres = ( finalExtent.width() ) / width;
double yres = ( finalExtent.height() ) / height;
int col = static_cast< int >( std::floor( ( point.x() - finalExtent.xMinimum() ) / xres ) );
int row = static_cast< int >( std::floor( ( finalExtent.yMaximum() - point.y() ) / yres ) );
double xMin = finalExtent.xMinimum() + col * xres;
double xMax = xMin + xres;
double yMax = finalExtent.yMaximum() - row * yres;
double yMin = yMax - yres;
QgsRectangle pixelExtent( xMin, yMin, xMax, yMax );
for ( int i = 1; i <= bandCount(); i++ )
{
std::unique_ptr< QgsRasterBlock > bandBlock( block( i, pixelExtent, 1, 1 ) );
if ( bandBlock )
{
double value = bandBlock->value( 0 );
results.insert( i, value );
}
else
{
results.insert( i, QVariant() );
}
}
return QgsRasterIdentifyResult( QgsRaster::IdentifyFormatValue, results );
}
double QgsRasterDataProvider::sample( const QgsPointXY &point, int band,
bool *ok, const QgsRectangle &boundingBox, int width, int height, int dpi )
{
if ( ok )
*ok = false;
const auto res = identify( point, QgsRaster::IdentifyFormatValue, boundingBox, width, height, dpi );
const QVariant value = res.results().value( band );
if ( !value.isValid() )
return std::numeric_limits<double>::quiet_NaN();
if ( ok )
*ok = true;
return value.toDouble( ok );
}
QString QgsRasterDataProvider::lastErrorFormat()
{
return QStringLiteral( "text/plain" );
}
bool QgsRasterDataProvider::writeBlock( QgsRasterBlock *block, int band, int xOffset, int yOffset )
{
if ( !block )
return false;
if ( !isEditable() )
{
QgsDebugMsg( QStringLiteral( "writeBlock() called on read-only provider." ) );
return false;
}
return write( block->bits(), band, block->width(), block->height(), xOffset, yOffset );
}
// typedef QList<QPair<QString, QString> > *pyramidResamplingMethods_t();
QList<QPair<QString, QString> > QgsRasterDataProvider::pyramidResamplingMethods( const QString &providerKey )
{
QList<QPair<QString, QString> > methods = QgsProviderRegistry::instance()->pyramidResamplingMethods( providerKey );
if ( methods.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "provider pyramidResamplingMethods returned no methods" ) );
}
return methods;
}
bool QgsRasterDataProvider::hasPyramids()
{
QList<QgsRasterPyramid> myPyramidList = buildPyramidList();
if ( myPyramidList.isEmpty() )
return false;
QList<QgsRasterPyramid>::iterator myRasterPyramidIterator;
for ( myRasterPyramidIterator = myPyramidList.begin();
myRasterPyramidIterator != myPyramidList.end();
++myRasterPyramidIterator )
{
if ( myRasterPyramidIterator->exists )
{
return true;
}
}
return false;
}
void QgsRasterDataProvider::setUserNoDataValue( int bandNo, const QgsRasterRangeList &noData )
{
if ( bandNo >= mUserNoDataValue.size() )
{
for ( int i = mUserNoDataValue.size(); i < bandNo; i++ )
{
mUserNoDataValue.append( QgsRasterRangeList() );
}
}
QgsDebugMsgLevel( QStringLiteral( "set %1 band %1 no data ranges" ).arg( noData.size() ), 4 );
if ( mUserNoDataValue[bandNo - 1] != noData )
{
// Clear statistics
int i = 0;
while ( i < mStatistics.size() )
{
if ( mStatistics.value( i ).bandNumber == bandNo )
{
mStatistics.removeAt( i );
mHistograms.removeAt( i );
}
else
{
i++;
}
}
mUserNoDataValue[bandNo - 1] = noData;
}
}
QgsRasterDataProvider *QgsRasterDataProvider::create( const QString &providerKey,
const QString &uri,
const QString &format, int nBands,
Qgis::DataType type,
int width, int height, double *geoTransform,
const QgsCoordinateReferenceSystem &crs,
const QStringList &createOptions )
{
QgsRasterDataProvider *ret = QgsProviderRegistry::instance()->createRasterDataProvider(
providerKey,
uri, format,
nBands, type, width,
height, geoTransform, crs, createOptions );
if ( !ret )
{
QgsDebugMsg( "Cannot resolve 'createRasterDataProviderFunction' function in " + providerKey + " provider" );
}
// TODO: it would be good to return invalid QgsRasterDataProvider
// with QgsError set, but QgsRasterDataProvider has pure virtual methods
return ret;
}
QString QgsRasterDataProvider::identifyFormatName( QgsRaster::IdentifyFormat format )
{
switch ( format )
{
case QgsRaster::IdentifyFormatValue:
return QStringLiteral( "Value" );
case QgsRaster::IdentifyFormatText:
return QStringLiteral( "Text" );
case QgsRaster::IdentifyFormatHtml:
return QStringLiteral( "Html" );
case QgsRaster::IdentifyFormatFeature:
return QStringLiteral( "Feature" );
default:
return QStringLiteral( "Undefined" );
}
}
QString QgsRasterDataProvider::identifyFormatLabel( QgsRaster::IdentifyFormat format )
{
switch ( format )
{
case QgsRaster::IdentifyFormatValue:
return tr( "Value" );
case QgsRaster::IdentifyFormatText:
return tr( "Text" );
case QgsRaster::IdentifyFormatHtml:
return tr( "Html" );
case QgsRaster::IdentifyFormatFeature:
return tr( "Feature" );
default:
return QStringLiteral( "Undefined" );
}
}
QgsRaster::IdentifyFormat QgsRasterDataProvider::identifyFormatFromName( const QString &formatName )
{
if ( formatName == QLatin1String( "Value" ) ) return QgsRaster::IdentifyFormatValue;
if ( formatName == QLatin1String( "Text" ) ) return QgsRaster::IdentifyFormatText;
if ( formatName == QLatin1String( "Html" ) ) return QgsRaster::IdentifyFormatHtml;
if ( formatName == QLatin1String( "Feature" ) ) return QgsRaster::IdentifyFormatFeature;
return QgsRaster::IdentifyFormatUndefined;
}
QgsRasterInterface::Capability QgsRasterDataProvider::identifyFormatToCapability( QgsRaster::IdentifyFormat format )
{
switch ( format )
{
case QgsRaster::IdentifyFormatValue:
return IdentifyValue;
case QgsRaster::IdentifyFormatText:
return IdentifyText;
case QgsRaster::IdentifyFormatHtml:
return IdentifyHtml;
case QgsRaster::IdentifyFormatFeature:
return IdentifyFeature;
default:
return NoCapabilities;
}
}
QList<double> QgsRasterDataProvider::nativeResolutions() const
{
return QList< double >();
}
bool QgsRasterDataProvider::ignoreExtents() const
{
return false;
}
bool QgsRasterDataProvider::userNoDataValuesContains( int bandNo, double value ) const
{
QgsRasterRangeList rangeList = mUserNoDataValue.value( bandNo - 1 );
return QgsRasterRange::contains( value, rangeList );
}
void QgsRasterDataProvider::copyBaseSettings( const QgsRasterDataProvider &other )
{
mDpi = other.mDpi;
mSrcNoDataValue = other.mSrcNoDataValue;
mSrcHasNoDataValue = other.mSrcHasNoDataValue;
mUseSrcNoDataValue = other.mUseSrcNoDataValue;
mUserNoDataValue = other.mUserNoDataValue;
mExtent = other.mExtent;
// copy temporal properties
mTemporalProperties->setIsActive( other.mTemporalProperties->isActive() );
mTemporalProperties->setTemporalRange( other.mTemporalProperties->temporalRange() );
mTemporalProperties->setFixedTemporalRange( other.mTemporalProperties->fixedTemporalRange() );
mTemporalProperties->setEnableTime( other.mTemporalProperties->isTimeEnabled() );
}
// ENDS