Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[feature][processing] Add new algorithm for retrieving basic raster
layer properties

This algorithm "Raster Layer Properties" extracts basic raster layer
properties such as the size in pixels, pixel dimensions (map units per
pixel), number of bands, and no data value. It is intended for use
as a means of extracting these useful properties to use as the
input values to other algorithms in a model - eg to allow to pass
an existing raster's pixel sizes over to a GDAL raster algorithm.
  • Loading branch information
nyalldawson committed Apr 23, 2021
1 parent 1db625b commit f9b3f0a
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -140,6 +140,7 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmrandompointsonlines.cpp
processing/qgsalgorithmrandomraster.cpp
processing/qgsalgorithmrasterfrequencybycomparisonoperator.cpp
processing/qgsalgorithmrasterlayerproperties.cpp
processing/qgsalgorithmrasterlayeruniquevalues.cpp
processing/qgsalgorithmrasterlogicalop.cpp
processing/qgsalgorithmrasterize.cpp
Expand Down
140 changes: 140 additions & 0 deletions src/analysis/processing/qgsalgorithmrasterlayerproperties.cpp
@@ -0,0 +1,140 @@
/***************************************************************************
qgsalgorithmrasterlayerproperties.cpp
---------------------
begin : April 2021
copyright : (C) 2021 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* 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 "qgsalgorithmrasterlayerproperties.h"
#include "qgsstringutils.h"
#include <QTextStream>

///@cond PRIVATE

QString QgsRasterLayerPropertiesAlgorithm::name() const
{
return QStringLiteral( "rasterlayerproperties" );
}

QString QgsRasterLayerPropertiesAlgorithm::displayName() const
{
return QObject::tr( "Raster layer properties" );
}

QStringList QgsRasterLayerPropertiesAlgorithm::tags() const
{
return QObject::tr( "extent,pixel,size,width,height" ).split( ',' );
}

QString QgsRasterLayerPropertiesAlgorithm::group() const
{
return QObject::tr( "Raster analysis" );
}

QString QgsRasterLayerPropertiesAlgorithm::groupId() const
{
return QStringLiteral( "rasteranalysis" );
}

void QgsRasterLayerPropertiesAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ),
QObject::tr( "Input layer" ) ) );
addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ),
QObject::tr( "Band number" ), QVariant(), QStringLiteral( "INPUT" ), true ) );

addOutput( new QgsProcessingOutputNumber( QStringLiteral( "X_MIN" ), QObject::tr( "Minimum x-coordinate" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "X_MAX" ), QObject::tr( "Maximum x-coordinate" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "Y_MIN" ), QObject::tr( "Minimum y-coordinate" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "Y_MAX" ), QObject::tr( "Maximum y-coordinate" ) ) );
addOutput( new QgsProcessingOutputString( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "PIXEL_WIDTH" ), QObject::tr( "Pixel size (width) in map units" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "PIXEL_HEIGHT" ), QObject::tr( "Pixel size (height) in map units" ) ) );
addOutput( new QgsProcessingOutputString( QStringLiteral( "CRS_AUTHID" ), QObject::tr( "CRS authority identifier" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "WIDTH_IN_PIXELS" ), QObject::tr( "Width in pixels" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "HEIGHT_IN_PIXELS" ), QObject::tr( "Height in pixels" ) ) );
addOutput( new QgsProcessingOutputBoolean( QStringLiteral( "HAS_NODATA_VALUE" ), QObject::tr( "True if layer has a nodata value set" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NODATA_VALUE" ), QObject::tr( "Nodata value" ) ) );
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "BAND_COUNT" ), QObject::tr( "Number of bands in raster" ) ) );
}

QString QgsRasterLayerPropertiesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm returns basic properties of the given raster layer, including the extent, size in pixels and dimensions of pixels (in map units).\n\n"
"If an optional band number is specified then the nodata value for the selected band will also be returned." );
}

QgsRasterLayerPropertiesAlgorithm *QgsRasterLayerPropertiesAlgorithm::createInstance() const
{
return new QgsRasterLayerPropertiesAlgorithm();
}

bool QgsRasterLayerPropertiesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );

if ( !layer )
throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );

mBandCount = layer->bandCount();

if ( parameters.value( QStringLiteral( "BAND" ) ).isValid() )
{
const int band = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
if ( band < 1 || band > mBandCount )
throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( band )
.arg( layer->bandCount() ) );
mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
if ( mHasNoDataValue )
mNoDataValue = layer->dataProvider()->sourceNoDataValue( band );
}

mLayerWidth = layer->width();
mLayerHeight = layer->height();
mExtent = layer->extent();
mCrs = layer->crs();
mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();

return true;
}

QVariantMap QgsRasterLayerPropertiesAlgorithm::processAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
{
QVariantMap outputs;
outputs.insert( QStringLiteral( "EXTENT" ), mExtent.toString() );
outputs.insert( QStringLiteral( "X_MIN" ), mExtent.xMinimum() );
outputs.insert( QStringLiteral( "X_MAX" ), mExtent.xMaximum() );
outputs.insert( QStringLiteral( "Y_MIN" ), mExtent.yMinimum() );
outputs.insert( QStringLiteral( "Y_MAX" ), mExtent.yMaximum() );

outputs.insert( QStringLiteral( "PIXEL_WIDTH" ), mRasterUnitsPerPixelX );
outputs.insert( QStringLiteral( "PIXEL_HEIGHT" ), mRasterUnitsPerPixelY );

outputs.insert( QStringLiteral( "CRS_AUTHID" ), mCrs.authid() );
outputs.insert( QStringLiteral( "WIDTH_IN_PIXELS" ), mLayerWidth );
outputs.insert( QStringLiteral( "HEIGHT_IN_PIXELS" ), mLayerHeight );

outputs.insert( QStringLiteral( "HAS_NODATA_VALUE" ), mHasNoDataValue );
if ( mHasNoDataValue )
outputs.insert( QStringLiteral( "NODATA_VALUE" ), mNoDataValue );
outputs.insert( QStringLiteral( "BAND_COUNT" ), mBandCount );

return outputs;
}


///@endcond



71 changes: 71 additions & 0 deletions src/analysis/processing/qgsalgorithmrasterlayerproperties.h
@@ -0,0 +1,71 @@
/***************************************************************************
qgsalgorithmrasterlayerproperties.cpp
---------------------
begin : April 2021
copyright : (C) 2021 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#ifndef QGSALGORITHMRASTERLAYERPROPERTIES_H
#define QGSALGORITHMRASTERLAYERPROPERTIES_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"

///@cond PRIVATE

/**
* Native raster layer properties algorithm.
*/
class QgsRasterLayerPropertiesAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsRasterLayerPropertiesAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QgsRasterLayerPropertiesAlgorithm *createInstance() const override SIP_FACTORY;

protected:

bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

private:

int mBandCount = 0;
bool mHasNoDataValue = false;
QVariant mNoDataValue;
int mLayerWidth;
int mLayerHeight;
QgsRectangle mExtent;
QgsCoordinateReferenceSystem mCrs;
double mRasterUnitsPerPixelX;
double mRasterUnitsPerPixelY;

};

///@endcond PRIVATE

#endif // QGSALGORITHMRASTERLAYERPROPERTIES_H


2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -136,6 +136,7 @@
#include "qgsalgorithmrandompointsonlines.h"
#include "qgsalgorithmrandomraster.h"
#include "qgsalgorithmrasterfrequencybycomparisonoperator.h"
#include "qgsalgorithmrasterlayerproperties.h"
#include "qgsalgorithmrasterlayeruniquevalues.h"
#include "qgsalgorithmrasterlogicalop.h"
#include "qgsalgorithmrasterize.h"
Expand Down Expand Up @@ -399,6 +400,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsRasterFrequencyByEqualOperatorAlgorithm() );
addAlgorithm( new QgsRasterFrequencyByGreaterThanOperatorAlgorithm() );
addAlgorithm( new QgsRasterFrequencyByLessThanOperatorAlgorithm() );
addAlgorithm( new QgsRasterLayerPropertiesAlgorithm() );
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
addAlgorithm( new QgsRasterLayerZonalStatsAlgorithm() );
addAlgorithm( new QgsRasterLogicalAndAlgorithm() );
Expand Down
54 changes: 54 additions & 0 deletions tests/src/analysis/testqgsprocessingalgs.cpp
Expand Up @@ -79,6 +79,7 @@ class TestQgsProcessingAlgs: public QObject
void cleanup() {} // will be called after every testfunction.
void saveFeaturesAlg();
void packageAlg();
void rasterLayerProperties();
void exportToSpreadsheetXlsx();
void exportToSpreadsheetOds();
void exportToSpreadsheetOptions();
Expand Down Expand Up @@ -460,6 +461,59 @@ void TestQgsProcessingAlgs::packageAlg()
QCOMPARE( selectedPolygonsPackagedLayer->featureCount(), 10 ); // With enabled SELECTED_FEATURES_ONLY all features should be saved when there is no selection
}

void TestQgsProcessingAlgs::rasterLayerProperties()
{
std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:rasterlayerproperties" ) ) );

QString myDataPath( TEST_DATA_DIR ); //defined in CMakeLists.txt

std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();

QVariantMap parameters;

parameters.insert( QStringLiteral( "INPUT" ), QVariant( myDataPath + "/landsat.tif" ) );

//run alg...
bool ok = false;
QgsProcessingFeedback feedback;
QVariantMap results;

results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );

QCOMPARE( results.value( QStringLiteral( "X_MIN" ) ).toDouble(), 781662.375 );
QCOMPARE( results.value( QStringLiteral( "X_MAX" ) ).toDouble(), 793062.375 );
QCOMPARE( results.value( QStringLiteral( "Y_MIN" ) ).toDouble(), 3339523.125 );
QCOMPARE( results.value( QStringLiteral( "Y_MAX" ) ).toDouble(), 3350923.125 );
QCOMPARE( results.value( QStringLiteral( "EXTENT" ) ).toString(), QStringLiteral( "781662.3750000000000000,3339523.1250000000000000 : 793062.3750000000000000,3350923.1250000000000000" ) );
QCOMPARE( results.value( QStringLiteral( "PIXEL_WIDTH" ) ).toDouble(), 57.0 );
QCOMPARE( results.value( QStringLiteral( "PIXEL_HEIGHT" ) ).toDouble(), 57.0 );
QCOMPARE( results.value( QStringLiteral( "CRS_AUTHID" ) ).toString(), QStringLiteral( "EPSG:32633" ) );
QCOMPARE( results.value( QStringLiteral( "WIDTH_IN_PIXELS" ) ).toInt(), 200 );
QCOMPARE( results.value( QStringLiteral( "HEIGHT_IN_PIXELS" ) ).toInt(), 200 );
QCOMPARE( results.value( QStringLiteral( "BAND_COUNT" ) ).toInt(), 9 );

parameters.insert( QStringLiteral( "INPUT" ), QVariant( myDataPath + "/raster/valueRas3_float64.asc" ) );
parameters.insert( QStringLiteral( "BAND" ), 1 );
ok = false;
results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );

QCOMPARE( results.value( QStringLiteral( "X_MIN" ) ).toDouble(), 0.0 );
QCOMPARE( results.value( QStringLiteral( "X_MAX" ) ).toDouble(), 4.0 );
QCOMPARE( results.value( QStringLiteral( "Y_MIN" ) ).toDouble(), 0.0 );
QCOMPARE( results.value( QStringLiteral( "Y_MAX" ) ).toDouble(), 4.0 );
QCOMPARE( results.value( QStringLiteral( "EXTENT" ) ).toString(), QStringLiteral( "0.0000000000000000,0.0000000000000000 : 4.0000000000000000,4.0000000000000000" ) );
QCOMPARE( results.value( QStringLiteral( "PIXEL_WIDTH" ) ).toDouble(), 1.0 );
QCOMPARE( results.value( QStringLiteral( "PIXEL_HEIGHT" ) ).toDouble(), 1.0 );
QCOMPARE( results.value( QStringLiteral( "CRS_AUTHID" ) ).toString(), QStringLiteral( "" ) );
QCOMPARE( results.value( QStringLiteral( "WIDTH_IN_PIXELS" ) ).toInt(), 4 );
QCOMPARE( results.value( QStringLiteral( "HEIGHT_IN_PIXELS" ) ).toInt(), 4 );
QCOMPARE( results.value( QStringLiteral( "BAND_COUNT" ) ).toInt(), 1 );
QCOMPARE( results.value( QStringLiteral( "HAS_NODATA_VALUE" ) ).toInt(), 1 );
QCOMPARE( results.value( QStringLiteral( "NODATA_VALUE" ) ).toInt(), -9999 );
}

void TestQgsProcessingAlgs::exportToSpreadsheetXlsx()
{
if ( QgsTest::isCIRun() )
Expand Down

0 comments on commit f9b3f0a

Please sign in to comment.