Skip to content

Commit

Permalink
First raster drawn!
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso committed Jan 10, 2020
1 parent 25972ab commit f822d45
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 20 deletions.
201 changes: 190 additions & 11 deletions src/providers/postgres/qgspostgresrasterprovider.cpp
Expand Up @@ -14,6 +14,7 @@
* *
***************************************************************************/

#include <cstring>
#include "qgspostgresrasterprovider.h"
#include "qgspostgrestransaction.h"
#include "qgsmessagelog.h"
Expand All @@ -32,14 +33,11 @@ QgsPostgresRasterProvider::QgsPostgresRasterProvider( const QString &uri, const
mSchemaName = mUri.schema();
mTableName = mUri.table();

/* deduced
mRasterColumn = mUri.geometryColumn();
if ( mRasterColumn.isEmpty() )
{
mRasterColumn = QStringLiteral( "rast" );
}
*/

mSqlWhereClause = mUri.sql();
mRequestedSrid = mUri.srid();

Expand Down Expand Up @@ -108,9 +106,26 @@ QgsPostgresRasterProvider::QgsPostgresRasterProvider( const QgsPostgresRasterPro
: QgsRasterDataProvider( other.dataSourceUri(), providerOptions )
, mValid( other.mValid )
, mCrs( other.mCrs )
, mUri( other.mUri )
, mIsQuery( other.mIsQuery )
, mTableName( other.mTableName )
, mQuery( other.mQuery )
, mRasterColumn( other.mRasterColumn )
, mSchemaName( other.mSchemaName )
, mSqlWhereClause( other.mSqlWhereClause )
, mExtent( other.mExtent )
, mUseEstimatedMetadata( other.mUseEstimatedMetadata )
, mDataTypes( other.mDataTypes )
, mDataSizes( other.mDataSizes )
, mNoDataValues( other.mNoDataValues )
, mBandCount( other.mBandCount )
, mIsTiled( other.mIsTiled )
, mIsOutOfDb( other.mIsOutOfDb )
, mWidth( other.mWidth )
, mHeight( other.mHeight )
, mConnectionRO( other.mConnectionRO )
, mConnectionRW( other.mConnectionRW )
{

}

QgsPostgresRasterProvider::~QgsPostgresRasterProvider()
Expand Down Expand Up @@ -177,8 +192,149 @@ QString QgsPostgresRasterProvider::description() const
return QgsPostgresRasterProvider::PG_RASTER_PROVIDER_DESCRIPTION;
}

bool QgsPostgresRasterProvider::readBlock( int bandNo, const QgsRectangle &viewExtent, int width, int height, void *data, QgsRasterBlockFeedback *feedback )
bool QgsPostgresRasterProvider::readBlock( int bandNo, const QgsRectangle &viewExtent, int width, int height, void *data, QgsRasterBlockFeedback * )
{
if ( bandNo > mBandCount )
{
// TODO: log
return false;
}
// Fetch data from backend
QString sql;
const bool isSingleValue { width == 1 && height == 1 };
if ( isSingleValue )
{
sql = QStringLiteral( "SELECT ST_Value( ST_Union( %1, %2), ST_GeomFromText( %3, %4 ), FALSE ) FROM %5" )
.arg( quotedIdentifier( mRasterColumn ) )
.arg( bandNo )
.arg( quotedValue( viewExtent.center().asWkt() ) )
.arg( mCrs.postgisSrid() )
.arg( mQuery );
}
else
{
// TODO: resample if width and height are different from x/y size
sql = QStringLiteral( "SELECT ST_AsBinary( ST_Resize( ST_Clip ( ST_Union( %1, %2 ), %2, ST_GeomFromText( %4, %5 ), %6 ), %8, %9 ) ) FROM %3 "
"WHERE ST_Intersects( %1, ST_GeomFromText( %4, %5 ) )" )
.arg( quotedIdentifier( mRasterColumn ) )
.arg( bandNo )
.arg( mQuery )
.arg( quotedValue( viewExtent.asWktPolygon() ) )
.arg( mCrs.postgisSrid() )
.arg( mNoDataValues[ static_cast<unsigned long>( bandNo - 1 ) ] )
.arg( width )
.arg( height );
}
QgsDebugMsg( QStringLiteral( "Reading raster block: %1" ).arg( sql ) );
QgsPostgresResult result( connectionRO()->PQexec( sql ) );
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
{
QgsMessageLog::logMessage( tr( "Unable to access the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
.arg( mQuery,
result.PQresultErrorMessage(),
sql ), tr( "PostGIS" ) );
return false;
}

if ( isSingleValue )
{
bool ok;
const QString val { result.PQgetvalue( 0, 0 ) };
const Qgis::DataType dataType { mDataTypes[ bandNo - 1 ] };
{
if ( dataType == Qgis::DataType::Byte )
{
const unsigned short byte { val.toUShort( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &byte, sizeof( unsigned short ) );
}
else if ( dataType == Qgis::DataType::UInt16 )
{
const unsigned int uint { val.toUInt( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &uint, sizeof( unsigned int ) );
}
else if ( dataType == Qgis::DataType::UInt32 )
{
const unsigned long ulong { val.toULong( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &ulong, sizeof( unsigned long ) );
}
else if ( dataType == Qgis::DataType::Int16 )
{
const int _int { val.toInt( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &_int, sizeof( int ) );
}
else if ( dataType == Qgis::DataType::Int32 )
{
const long _long { val.toInt( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &_long, sizeof( long ) );
}
else if ( dataType == Qgis::DataType::Float32 )
{
const float _float { val.toFloat( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &_float, sizeof( float ) );
}
else if ( dataType == Qgis::DataType::Float64 )
{
const double _double { val.toDouble( &ok ) };
if ( ! ok )
{
// TODO: log
return false;
}
std::memcpy( data, &_double, sizeof( double ) );
}
else
{
QgsMessageLog::logMessage( QStringLiteral( "Unknown data type" ), QStringLiteral( "PostGIS" ), Qgis::Warning );
return false;
}
}
}
else
{
const QByteArray bits { result.PQgetvalue( 0, 0 ).toAscii() };
const size_t dataSizeBytes { static_cast<size_t>( width *height *mDataSizes[ static_cast<unsigned long>( bandNo - 1 ) ] ) };
// header length is 132, plus \x
if ( bits.length() != 2 + 132 + dataSizeBytes * 2 )
{
QgsMessageLog::logMessage( QStringLiteral( "Wrong data size: expected = %1, actual = %2" )
.arg( 2 + 132 + dataSizeBytes * 2 )
.arg( bits.length() ), QStringLiteral( "PostGIS" ), Qgis::Warning );
return false;
}
// Decode
const QByteArray bin { QByteArray::fromHex( bits.mid( 2 + 132 ) ) };
std::memcpy( data, bin.constData(), dataSizeBytes );
}
return true;
}

Expand Down Expand Up @@ -227,7 +383,8 @@ Qgis::DataType QgsPostgresRasterProvider::dataType( int bandNo ) const
// TODO: raise or at least log!
return Qgis::DataType::UnknownDataType;
}
return mDataTypes[ bandNo ];
// Band is 1-based
return mDataTypes[ bandNo - 1 ];
}

int QgsPostgresRasterProvider::bandCount() const
Expand Down Expand Up @@ -305,8 +462,8 @@ bool QgsPostgresRasterProvider::getDetails()
if ( mUseEstimatedMetadata )
{
QString sql { QStringLiteral( "SELECT r_raster_column, srid,"
"num_bands, pixel_types, nodata_values, extent, out_db,"
"scale_x, scale_y, blocksize_x, blocksize_y, same_alignment,"
"num_bands, pixel_types, nodata_values, ST_AsBinary(extent), blocksize_x, blocksize_y,"
"out_db, scale_x, scale_y, same_alignment,"
"regular_blocking,"
"spatial_index FROM raster_columns WHERE "
"r_table_name = %1 AND r_table_schema = %2 AND r_table_catalog = %3" )
Expand Down Expand Up @@ -391,6 +548,7 @@ bool QgsPostgresRasterProvider::getDetails()
// TODO: log
}
mDataTypes.push_back( type );
mDataSizes.push_back( QgsRasterBlock::typeSize( type ) );
mNoDataValues.push_back( noDataValues.at( i++ ).toDouble( &ok ) );
if ( ! ok )
{
Expand All @@ -400,16 +558,28 @@ bool QgsPostgresRasterProvider::getDetails()
}
// Extent
QgsPolygon p;
const QByteArray hexAscii { result.PQgetvalue( 0, 5 ).toAscii() };
qDebug() << hexAscii;
qDebug() << QByteArray::fromHex( hexAscii );
// Strip \x
const QByteArray hexAscii { result.PQgetvalue( 0, 5 ).toAscii().mid( 2 ) };
QgsConstWkbPtr ptr { QByteArray::fromHex( hexAscii ) };
if ( ! p.fromWkb( ptr ) )
{
// TODO: log
return false;
}
mExtent = p.boundingBox();
// Size
mWidth = result.PQgetvalue( 0, 6 ).toInt( &ok );
if ( ! ok )
{
// TODO: log
return false;
}
mHeight = result.PQgetvalue( 0, 7 ).toInt( &ok );
if ( ! ok )
{
// TODO: log
return false;
}
return true;
}
else
Expand All @@ -424,6 +594,15 @@ bool QgsPostgresRasterProvider::getDetails()
}
}

int QgsPostgresRasterProvider::xSize() const
{
return mWidth;
}

int QgsPostgresRasterProvider::ySize() const
{
return mHeight;
}

Qgis::DataType QgsPostgresRasterProvider::sourceDataType( int bandNo ) const
{
Expand Down
14 changes: 12 additions & 2 deletions src/providers/postgres/qgspostgresrasterprovider.h
Expand Up @@ -86,14 +86,19 @@ class QgsPostgresRasterProvider : public QgsRasterDataProvider
QString mErrorTitle;
//! Data type for each band
std::vector<Qgis::DataType> mDataTypes;
//! Data size in bytes for each band
std::vector<int> mDataSizes;
//! Nodata values for each band
std::vector<double> mNoDataValues;
//! Band count
int mBandCount = 0;
//! If is tiled
bool isTiled = false;
bool mIsTiled = false;
//! If is out of DB
bool isOutOfDb = false;
bool mIsOutOfDb = false;
// size
int mWidth = 0;
int mHeight = 0;

QString mDetectedSrid; //!< Spatial reference detected in the database
QString mRequestedSrid; //!< Spatial reference requested in the uri
Expand All @@ -112,6 +117,11 @@ class QgsPostgresRasterProvider : public QgsRasterDataProvider
static QString quotedJsonValue( const QVariant &value ) { return QgsPostgresConn::quotedJsonValue( value ); }
static QString quotedByteaValue( const QVariant &value );


// QgsRasterInterface interface
public:
int xSize() const override;
int ySize() const override;
};


Expand Down
27 changes: 26 additions & 1 deletion tests/src/python/test_provider_postgresraster.py
Expand Up @@ -62,10 +62,35 @@ def testExtent(self):
extent = self.rl.extent()
self.assertEqual(extent, QgsRectangle(4080050, 2430625, 4080200, 2430750))

def testSize(self):
self.assertEqual(self.source.xSize(), 6)
self.assertEqual(self.source.ySize(), 5)

def testCrs(self):
self.assertEqual(self.source.crs().authid(), 'EPSG:3035')

def testGetData(self):
identify = self.source.identify(QgsPointXY(4080137.9, 2430687.9), QgsRaster.IdentifyFormatValue)
expected = 192.51044
self.assertEqual(identify.results()[1], expected)
self.assertAlmostEqual(identify.results()[1], expected, 4)

def testBlock(self):
expected = b"6a610843880b0e431cc2194306342543b7633c43861858436e0a1143bbad194359612743a12b334317be4343dece59432b621b43f0e42843132b3843ac824043e6cf48436e465a435c4d2d430fa63d43f87a4843b5494a4349454e4374f35b43906e41433ab54c43b056504358575243b1ec574322615f43"
block = self.source.block(1, self.rl.extent(), 6, 5)
actual = block.data().toHex()
self.assertEqual(len(actual), len(expected))
self.assertEqual(actual, expected)

from IPython import embed
embed()
print(self.rl.publicSource())

extent = QgsRectangle.fromWkt('POLYGON((4080090 2430646, 4080161 2430646, 4080161 2430685, 4080090 2430685, 4080090 2430646))')
block = self.source.block(1, extent, 2, 1)
expected = b'f0e42843e6cf4843'
actual = block.data().toHex()
self.assertEqual(len(actual), len(expected))
self.assertEqual(actual, expected)


if __name__ == '__main__':
Expand Down
11 changes: 5 additions & 6 deletions tests/testdata/provider/testdata_pg_raster.sql
Expand Up @@ -25,14 +25,12 @@ INSERT INTO qgis_test."Raster1" (name, "Rast") SELECT
1, '8BUI', 0.0, NULL
);


BEGIN;
CREATE TABLE "aspect_clipped_gpu_mini" ("rid" serial PRIMARY KEY,"rast" raster,"filename" text);
CREATE TABLE "o_2_aspect_clipped_gpu_mini" ("rid" serial PRIMARY KEY,"rast" raster,"filename" text);
CREATE TABLE "o_4_aspect_clipped_gpu_mini" ("rid" serial PRIMARY KEY,"rast" raster,"filename" text);
INSERT INTO "aspect_clipped_gpu_mini" ("rast","filename") VALUES ('0100000100000000000000394000000000000039C000000000D9204F41000000008F8B42410000000000000000000000000000000029230000060005004A003C1CC66A610843880B0E431CC2194306342543B7633C43861858436E0A1143BBAD194359612743A12B334317BE4343DECE59432B621B43F0E42843132B3843AC824043E6CF48436E465A435C4D2D430FA63D43F87A4843B5494A4349454E4374F35B43906E41433AB54C43B056504358575243B1EC574322615F43'::raster,'aspect_clipped_gpu_mini.tif');
INSERT INTO "o_2_aspect_clipped_gpu_mini" ("rast","filename") VALUES ('0100000100000000000000494000000000000049C000000000D9204F41000000008F8B42410000000000000000000000000000000029230000030003004A003C1CC6880B0E430634254386185843F0E42843AC8240436E465A433AB54C435857524322615F43'::raster,'aspect_clipped_gpu_mini.tif');
INSERT INTO "o_4_aspect_clipped_gpu_mini" ("rast","filename") VALUES ('0100000100000000000000594000000000000059C000000000D9204F41000000008F8B42410000000000000000000000000000000029230000020001004A003C1CC6F0E42843E6CF4843'::raster,'aspect_clipped_gpu_mini.tif');
INSERT INTO "aspect_clipped_gpu_mini" ("rast","filename") VALUES ('0100000100000000000000394000000000000039C000000000D9204F41000000008F8B424100000000000000000000000000000000DB0B0000060005004A003C1CC66A610843880B0E431CC2194306342543B7633C43861858436E0A1143BBAD194359612743A12B334317BE4343DECE59432B621B43F0E42843132B3843AC824043E6CF48436E465A435C4D2D430FA63D43F87A4843B5494A4349454E4374F35B43906E41433AB54C43B056504358575243B1EC574322615F43'::raster,'aspect_clipped_gpu_mini.tif');
INSERT INTO "o_2_aspect_clipped_gpu_mini" ("rast","filename") VALUES ('0100000100000000000000494000000000000049C000000000D9204F41000000008F8B424100000000000000000000000000000000DB0B0000030003004A003C1CC6880B0E430634254386185843F0E42843AC8240436E465A433AB54C435857524322615F43'::raster,'aspect_clipped_gpu_mini.tif');
INSERT INTO "o_4_aspect_clipped_gpu_mini" ("rast","filename") VALUES ('0100000100000000000000594000000000000059C000000000D9204F41000000008F8B424100000000000000000000000000000000DB0B0000020001004A003C1CC6F0E42843E6CF4843'::raster,'aspect_clipped_gpu_mini.tif');
CREATE INDEX ON "aspect_clipped_gpu_mini" USING gist (st_convexhull("rast"));
ANALYZE "aspect_clipped_gpu_mini";
CREATE INDEX ON "o_2_aspect_clipped_gpu_mini" USING gist (st_convexhull("rast"));
Expand All @@ -44,4 +42,5 @@ SELECT AddRasterConstraints('','o_2_aspect_clipped_gpu_mini','rast',TRUE,TRUE,TR
SELECT AddRasterConstraints('','o_4_aspect_clipped_gpu_mini','rast',TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE);
SELECT AddOverviewConstraints('','o_2_aspect_clipped_gpu_mini','rast','','aspect_clipped_gpu_mini','rast',2);
SELECT AddOverviewConstraints('','o_4_aspect_clipped_gpu_mini','rast','','aspect_clipped_gpu_mini','rast',4);
END;


0 comments on commit f822d45

Please sign in to comment.