Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmrasterlayeruniquevalues.cpp
3 : : ---------------------
4 : : begin : April 2017
5 : : copyright : (C) 2017 by Mathieu Pellerin
6 : : email : nirvn dot asia at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsalgorithmrasterlayeruniquevalues.h"
19 : : #include "qgsstringutils.h"
20 : : #include <QTextStream>
21 : :
22 : : ///@cond PRIVATE
23 : :
24 : 0 : QString QgsRasterLayerUniqueValuesReportAlgorithm::name() const
25 : : {
26 : 0 : return QStringLiteral( "rasterlayeruniquevaluesreport" );
27 : : }
28 : :
29 : 0 : QString QgsRasterLayerUniqueValuesReportAlgorithm::displayName() const
30 : : {
31 : 0 : return QObject::tr( "Raster layer unique values report" );
32 : : }
33 : :
34 : 0 : QStringList QgsRasterLayerUniqueValuesReportAlgorithm::tags() const
35 : : {
36 : 0 : return QObject::tr( "count,area,statistics" ).split( ',' );
37 : 0 : }
38 : :
39 : 0 : QString QgsRasterLayerUniqueValuesReportAlgorithm::group() const
40 : : {
41 : 0 : return QObject::tr( "Raster analysis" );
42 : : }
43 : :
44 : 0 : QString QgsRasterLayerUniqueValuesReportAlgorithm::groupId() const
45 : : {
46 : 0 : return QStringLiteral( "rasteranalysis" );
47 : : }
48 : :
49 : 0 : void QgsRasterLayerUniqueValuesReportAlgorithm::initAlgorithm( const QVariantMap & )
50 : : {
51 : 0 : addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ),
52 : 0 : QObject::tr( "Input layer" ) ) );
53 : 0 : addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ),
54 : 0 : QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
55 : 0 : addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML_FILE" ),
56 : 0 : QObject::tr( "Unique values report" ), QObject::tr( "HTML files (*.html)" ), QVariant(), true ) );
57 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_TABLE" ),
58 : 0 : QObject::tr( "Unique values table" ), QgsProcessing::TypeVector, QVariant(), true, false ) );
59 : :
60 : 0 : addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
61 : 0 : addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
62 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
63 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
64 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_PIXEL_COUNT" ), QObject::tr( "Total pixel count" ) ) );
65 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NODATA_PIXEL_COUNT" ), QObject::tr( "NODATA pixel count" ) ) );
66 : 0 : }
67 : :
68 : 0 : QString QgsRasterLayerUniqueValuesReportAlgorithm::shortHelpString() const
69 : : {
70 : 0 : return QObject::tr( "This algorithm returns the count and area of each unique value in a given raster layer." );
71 : : }
72 : :
73 : 0 : QgsRasterLayerUniqueValuesReportAlgorithm *QgsRasterLayerUniqueValuesReportAlgorithm::createInstance() const
74 : : {
75 : 0 : return new QgsRasterLayerUniqueValuesReportAlgorithm();
76 : 0 : }
77 : :
78 : 0 : bool QgsRasterLayerUniqueValuesReportAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
79 : : {
80 : 0 : QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
81 : 0 : int band = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
82 : :
83 : 0 : if ( !layer )
84 : 0 : throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
85 : :
86 : 0 : mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
87 : 0 : if ( mBand < 1 || mBand > layer->bandCount() )
88 : 0 : throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand )
89 : 0 : .arg( layer->bandCount() ) );
90 : :
91 : 0 : mInterface.reset( layer->dataProvider()->clone() );
92 : 0 : mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
93 : 0 : mLayerWidth = layer->width();
94 : 0 : mLayerHeight = layer->height();
95 : 0 : mExtent = layer->extent();
96 : 0 : mCrs = layer->crs();
97 : 0 : mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
98 : 0 : mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
99 : 0 : mSource = layer->source();
100 : :
101 : 0 : return true;
102 : 0 : }
103 : :
104 : 0 : QVariantMap QgsRasterLayerUniqueValuesReportAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
105 : : {
106 : 0 : QString outputFile = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT_HTML_FILE" ), context );
107 : :
108 : 0 : QString areaUnit = QgsUnitTypes::toAbbreviatedString( QgsUnitTypes::distanceToAreaUnit( mCrs.mapUnits() ) );
109 : :
110 : 0 : QString tableDest;
111 : 0 : std::unique_ptr< QgsFeatureSink > sink;
112 : 0 : if ( parameters.contains( QStringLiteral( "OUTPUT_TABLE" ) ) && parameters.value( QStringLiteral( "OUTPUT_TABLE" ) ).isValid() )
113 : : {
114 : 0 : QgsFields outFields;
115 : 0 : outFields.append( QgsField( QStringLiteral( "value" ), QVariant::Double, QString(), 20, 8 ) );
116 : 0 : outFields.append( QgsField( QStringLiteral( "count" ), QVariant::Int, QString(), 20 ) );
117 : 0 : outFields.append( QgsField( areaUnit.replace( QStringLiteral( "²" ), QStringLiteral( "2" ) ), QVariant::Double, QString(), 20, 8 ) );
118 : 0 : sink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT_TABLE" ), context, tableDest, outFields, QgsWkbTypes::NoGeometry, QgsCoordinateReferenceSystem() ) );
119 : 0 : if ( !sink )
120 : 0 : throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_TABLE" ) ) );
121 : 0 : }
122 : :
123 : 0 : QHash< double, qgssize > uniqueValues;
124 : 0 : qgssize noDataCount = 0;
125 : :
126 : 0 : qgssize layerSize = static_cast< qgssize >( mLayerWidth ) * static_cast< qgssize >( mLayerHeight );
127 : 0 : int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH;
128 : 0 : int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT;
129 : 0 : int nbBlocksWidth = std::ceil( 1.0 * mLayerWidth / maxWidth );
130 : 0 : int nbBlocksHeight = std::ceil( 1.0 * mLayerHeight / maxHeight );
131 : 0 : int nbBlocks = nbBlocksWidth * nbBlocksHeight;
132 : :
133 : 0 : QgsRasterIterator iter( mInterface.get() );
134 : 0 : iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
135 : :
136 : 0 : int iterLeft = 0;
137 : 0 : int iterTop = 0;
138 : 0 : int iterCols = 0;
139 : 0 : int iterRows = 0;
140 : 0 : bool isNoData = false;
141 : 0 : std::unique_ptr< QgsRasterBlock > rasterBlock;
142 : 0 : while ( iter.readNextRasterPart( mBand, iterCols, iterRows, rasterBlock, iterLeft, iterTop ) )
143 : : {
144 : 0 : feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
145 : 0 : for ( int row = 0; row < iterRows; row++ )
146 : : {
147 : 0 : if ( feedback->isCanceled() )
148 : 0 : break;
149 : 0 : for ( int column = 0; column < iterCols; column++ )
150 : : {
151 : 0 : double value = rasterBlock->valueAndNoData( row, column, isNoData );
152 : 0 : if ( mHasNoDataValue && isNoData )
153 : : {
154 : 0 : noDataCount++;
155 : 0 : }
156 : : else
157 : : {
158 : 0 : uniqueValues[ value ]++;
159 : : }
160 : 0 : }
161 : 0 : }
162 : : }
163 : :
164 : 0 : QMap< double, qgssize > sortedUniqueValues;
165 : 0 : for ( auto it = uniqueValues.constBegin(); it != uniqueValues.constEnd(); ++it )
166 : : {
167 : 0 : sortedUniqueValues.insert( it.key(), it.value() );
168 : 0 : }
169 : :
170 : 0 : QVariantMap outputs;
171 : 0 : outputs.insert( QStringLiteral( "EXTENT" ), mExtent.toString() );
172 : 0 : outputs.insert( QStringLiteral( "CRS_AUTHID" ), mCrs.authid() );
173 : 0 : outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), mLayerWidth );
174 : 0 : outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), mLayerHeight );
175 : 0 : outputs.insert( QStringLiteral( "TOTAL_PIXEL_COUNT" ), layerSize );
176 : 0 : outputs.insert( QStringLiteral( "NODATA_PIXEL_COUNT" ), noDataCount );
177 : :
178 : 0 : double pixelArea = mRasterUnitsPerPixelX * mRasterUnitsPerPixelY;
179 : :
180 : 0 : if ( !outputFile.isEmpty() )
181 : : {
182 : 0 : QFile file( outputFile );
183 : 0 : if ( file.open( QIODevice::WriteOnly | QIODevice::Text ) )
184 : : {
185 : 0 : const QString encodedAreaUnit = QgsStringUtils::ampersandEncode( areaUnit );
186 : :
187 : 0 : QTextStream out( &file );
188 : : #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
189 : 0 : out.setCodec( "UTF-8" );
190 : : #endif
191 : 0 : out << QStringLiteral( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/></head><body>\n" );
192 : 0 : out << QStringLiteral( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Analyzed file" ), mSource, QObject::tr( "band" ) ).arg( mBand );
193 : 0 : out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Extent" ), mExtent.toString() );
194 : 0 : out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Projection" ), mCrs.userFriendlyIdentifier() );
195 : 0 : out << QObject::tr( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Width in pixels" ) ).arg( mLayerWidth ).arg( QObject::tr( "units per pixel" ) ).arg( mRasterUnitsPerPixelX );
196 : 0 : out << QObject::tr( "<p>%1: %2 (%3 %4)</p>\n" ).arg( QObject::tr( "Height in pixels" ) ).arg( mLayerHeight ).arg( QObject::tr( "units per pixel" ) ).arg( mRasterUnitsPerPixelY );
197 : 0 : out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "Total pixel count" ) ).arg( layerSize );
198 : 0 : if ( mHasNoDataValue )
199 : 0 : out << QObject::tr( "<p>%1: %2</p>\n" ).arg( QObject::tr( "NODATA pixel count" ) ).arg( noDataCount );
200 : 0 : out << QStringLiteral( "<table><tr><td>%1</td><td>%2</td><td>%3 (%4)</td></tr>\n" ).arg( QObject::tr( "Value" ), QObject::tr( "Pixel count" ), QObject::tr( "Area" ), encodedAreaUnit );
201 : :
202 : 0 : for ( auto it = sortedUniqueValues.constBegin(); it != sortedUniqueValues.constEnd(); ++it )
203 : : {
204 : 0 : double area = it.value() * pixelArea;
205 : 0 : out << QStringLiteral( "<tr><td>%1</td><td>%2</td><td>%3</td></tr>\n" ).arg( it.key() ).arg( it.value() ).arg( QString::number( area, 'g', 16 ) );
206 : 0 : }
207 : 0 : out << QStringLiteral( "</table>\n</body></html>" );
208 : 0 : outputs.insert( QStringLiteral( "OUTPUT_HTML_FILE" ), outputFile );
209 : 0 : }
210 : 0 : }
211 : :
212 : 0 : if ( sink )
213 : : {
214 : 0 : for ( auto it = sortedUniqueValues.constBegin(); it != sortedUniqueValues.constEnd(); ++it )
215 : : {
216 : 0 : QgsFeature f;
217 : 0 : double area = it.value() * pixelArea;
218 : 0 : f.setAttributes( QgsAttributes() << it.key() << it.value() << area );
219 : 0 : sink->addFeature( f, QgsFeatureSink::FastInsert );
220 : 0 : }
221 : 0 : outputs.insert( QStringLiteral( "OUTPUT_TABLE" ), tableDest );
222 : 0 : }
223 : :
224 : 0 : return outputs;
225 : 0 : }
226 : :
227 : :
228 : : ///@endcond
229 : :
230 : :
231 : :
|