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