1 /***************************************************************************
2 qgspostgresrasterutils.cpp - QgsPostgresRasterUtils
3
4 ---------------------
5 begin : 8.1.2020
6 copyright : (C) 2020 by Alessandro Pasotti
7 email : elpaso at itopen dot it
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17
18 #include "qgspostgresrasterutils.h"
19 #include "qgsmessagelog.h"
20
parseWkb(const QByteArray & wkb,int bandNo)21 QVariantMap QgsPostgresRasterUtils::parseWkb( const QByteArray &wkb, int bandNo )
22 {
23 QVariantMap result;
24 const int minWkbSize { 61 };
25 if ( wkb.size() < minWkbSize )
26 {
27 QgsMessageLog::logMessage( QStringLiteral( "Wrong wkb size: min expected = %1, actual = %2" )
28 .arg( minWkbSize )
29 .arg( wkb.size() ), QStringLiteral( "PostGIS" ), Qgis::MessageLevel::Critical );
30
31 return result;
32 }
33
34 /*
35 { name: 'endianness', byteOffset: 0, byteLength: 1, type: 'Uint8' },
36 { name: 'version', byteOffset: 1, byteLength: 2, type: 'Uint16' },
37 { name: 'nBands', byteOffset: 3, byteLength: 2, type: 'Uint16' },
38 { name: 'scaleX', byteOffset: 5, byteLength: 8, type: 'Float64' },
39 { name: 'scaleY', byteOffset: 13, byteLength: 8, type: 'Float64' },
40 { name: 'ipX', byteOffset: 21, byteLength: 8, type: 'Float64' },
41 { name: 'ipY', byteOffset: 29, byteLength: 8, type: 'Float64' },
42 { name: 'skewX', byteOffset: 37, byteLength: 8, type: 'Float64' },
43 { name: 'skewY', byteOffset: 45, byteLength: 8, type: 'Float64' },
44 { name: 'srid', byteOffset: 53, byteLength: 4, type: 'Int32' },
45 { name: 'width', byteOffset: 57, byteLength: 2, type: 'Uint16' },
46 { name: 'height', byteOffset: 59, byteLength: 2, type: 'Uint16' },
47 */
48
49 // Endianness
50 result[ QStringLiteral( "endianness" ) ] = static_cast<unsigned short int>( wkb[0] );
51 // NOTE: For now only little endian is supported
52 // TODO: endianness
53 Q_ASSERT( result[ QStringLiteral( "endianness" ) ] == 1 );
54 result[ QStringLiteral( "version" ) ] = *reinterpret_cast<const unsigned short int *>( &wkb.constData()[1] );
55 const unsigned short int nBands { *reinterpret_cast<const unsigned short int *>( &wkb.constData()[3] ) };
56 result[ QStringLiteral( "nBands" ) ] = nBands;
57 result[ QStringLiteral( "scaleX" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[5] );
58 result[ QStringLiteral( "scaleY" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[13] );
59 result[ QStringLiteral( "ipX" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[21] );
60 result[ QStringLiteral( "ipY" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[29] );
61 result[ QStringLiteral( "skewX" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[37] );
62 result[ QStringLiteral( "skewY" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[45] );
63 result[ QStringLiteral( "srid" ) ] = static_cast<int>( *reinterpret_cast<const long int *>( &wkb.constData()[53] ) );
64 result[ QStringLiteral( "width" ) ] = *reinterpret_cast<const unsigned short int *>( &wkb.constData()[57] );
65 result[ QStringLiteral( "height" ) ] = *reinterpret_cast<const unsigned short int *>( &wkb.constData()[59] );
66
67 // Band data starts at index 61
68 int offset = 61;
69
70 auto readBandHeader = [ & ]( )
71 {
72 result[ QStringLiteral( "pxType" ) ] = *reinterpret_cast<const unsigned short int *>( &wkb.constData()[offset] ) & 0x0F;
73 /*
74 | 0 'Bool1' // unsupported
75 | 1 'Uint2' // unsupported
76 | 2 'Uint4' // unsupported
77 | 3 'Int8'
78 | 4 'Uint8'
79 | 5 'Int16'
80 | 6 'Uint16'
81 | 7 'Int32'
82 | 8 'Uint32'
83 !!!!!!!!!!!!!!!!!!!!!!!!!!!
84 | 9 is missing!!!!
85 !!!!!!!!!!!!!!!!!!!!!!!!!!!
86 | 10 'Float32'
87 | 11 'Float64'
88 */
89 offset++;
90 int pxSize = 0; // in bytes
91 switch ( result[ QStringLiteral( "pxType" ) ].toInt() )
92 {
93 case 3: // int8
94 pxSize = 1;
95 result[ QStringLiteral( "nodata" ) ] = *reinterpret_cast<const short int *>( &wkb.constData()[ offset ] );
96 break;
97 case 4: // uint8
98 result[ QStringLiteral( "nodata" ) ] = *reinterpret_cast<const unsigned short int *>( &wkb.constData()[ offset ] );
99 pxSize = 1;
100 break;
101 case 5: // int16
102 result[ QStringLiteral( "nodata" ) ] = *reinterpret_cast<const int *>( &wkb.constData()[ offset ] );
103 pxSize = 2;
104 break;
105 case 6: // uint16
106 result[ QStringLiteral( "nodata" ) ] = *reinterpret_cast<const unsigned int *>( &wkb.constData()[ offset ] );
107 pxSize = 2;
108 break;
109 case 7: // int32
110 result[ QStringLiteral( "nodata" ) ] = static_cast<long long>( *reinterpret_cast<const long int *>( &wkb.constData()[ offset ] ) );
111 pxSize = 4;
112 break;
113 case 8: // uint32
114 result[ QStringLiteral( "nodata" ) ] = static_cast<unsigned long long>( *reinterpret_cast<const unsigned long int *>( &wkb.constData()[ offset ] ) );
115 pxSize = 4;
116 break;
117
118 // Note: 9 is missing from the specs
119
120 case 10: // float 32 bit
121 result[ QStringLiteral( "nodata" ) ] = *reinterpret_cast<const float *>( &wkb.constData()[ offset ] );
122 pxSize = 4;
123 break;
124 case 11: // double 64 bit
125 result[ QStringLiteral( "nodata" ) ] = *reinterpret_cast<const double *>( &wkb.constData()[ offset ] );
126 pxSize = 8;
127 break;
128 default:
129 result[ QStringLiteral( "nodata" ) ] = std::numeric_limits<double>::min();
130 QgsMessageLog::logMessage( QStringLiteral( "Unsupported pixel type: %1" )
131 .arg( result[ QStringLiteral( "pxType" ) ].toInt() ), QStringLiteral( "PostGIS" ), Qgis::MessageLevel::Critical );
132
133 }
134 result[ QStringLiteral( "pxSize" ) ] = pxSize;
135 offset += pxSize; // Init of band data
136 result[ QStringLiteral( "dataSize" ) ] = static_cast<unsigned int>( pxSize ) * result[ QStringLiteral( "width" ) ].toUInt() * result[ QStringLiteral( "height" ) ].toUInt();
137 };
138
139 if ( static_cast<unsigned int>( bandNo ) > nBands )
140 {
141 QgsMessageLog::logMessage( QStringLiteral( "Band number is not valid: %1 (nBands: %2" )
142 .arg( bandNo ).arg( nBands ), QStringLiteral( "PostGIS" ), Qgis::MessageLevel::Critical );
143 return result;
144 }
145
146 // Read bands (all bands if bandNo is 0)
147 for ( unsigned int bandCnt = 1; bandCnt <= ( bandNo == 0 ? nBands : static_cast<unsigned int>( bandNo ) ); ++bandCnt )
148 {
149 readBandHeader( );
150 if ( bandNo == 0 || static_cast<unsigned int>( bandNo ) == bandCnt )
151 {
152 result[ QStringLiteral( "band%1" ).arg( bandCnt )] = wkb.mid( offset, result[ QStringLiteral( "dataSize" ) ].toUInt() );
153 // Invert rows?
154 if ( result[ QStringLiteral( "scaleY" ) ].toDouble( ) > 0 )
155 {
156 const unsigned int numRows { result[ QStringLiteral( "height" ) ].toUInt() };
157 const auto rowSize { result[ QStringLiteral( "dataSize" ) ].toUInt() / numRows };
158 const QByteArray &oldBa { result[ QStringLiteral( "band%1" ).arg( bandCnt )].toByteArray() };
159 QByteArray ba;
160 for ( qlonglong rowOffset = ( numRows - 1 ) * rowSize; rowOffset >= 0; rowOffset -= rowSize )
161 {
162 ba.append( oldBa.mid( rowOffset, rowSize ) );
163 }
164 result[ QStringLiteral( "band%1" ).arg( bandCnt )] = ba;
165 }
166 }
167 else
168 {
169 // Skip
170 }
171 offset += result[ QStringLiteral( "dataSize" ) ].toUInt();
172 }
173 return result;
174 }
175