mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-04 00:06:46 -05:00
538 lines
18 KiB
C++
538 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 )
|
|
, mTemporalCapabilities( qgis::make_unique< QgsRasterDataProviderTemporalCapabilities >() )
|
|
{
|
|
|
|
}
|
|
|
|
QgsRasterDataProvider::QgsRasterDataProvider( const QString &uri, const ProviderOptions &options )
|
|
: QgsDataProvider( uri, options )
|
|
, QgsRasterInterface( nullptr )
|
|
, mTemporalCapabilities( qgis::make_unique< QgsRasterDataProviderTemporalCapabilities >() )
|
|
{
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
QgsRasterDataProviderTemporalCapabilities *QgsRasterDataProvider::temporalCapabilities() const
|
|
{
|
|
return mTemporalCapabilities.get();
|
|
}
|
|
|
|
const QgsRasterDataProviderTemporalCapabilities *QgsRasterDataProvider::temporalCapabilities() const
|
|
{
|
|
return mTemporalCapabilities.get();
|
|
}
|
|
|
|
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
|
|
if ( mTemporalCapabilities && other.mTemporalCapabilities )
|
|
{
|
|
*mTemporalCapabilities = *other.mTemporalCapabilities;
|
|
}
|
|
}
|
|
|
|
// ENDS
|