Skip to content

Commit 147a68c

Browse files
committed
Do not print excessive decimals when identifying value on a Float32 raster
Was reported in https://lists.osgeo.org/pipermail/qgis-user/2016-April/036045.html
1 parent 3d95712 commit 147a68c

File tree

7 files changed

+143
-6
lines changed

7 files changed

+143
-6
lines changed

src/core/qgis.h

+10
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,16 @@ inline bool qgsDoubleNear( double a, double b, double epsilon = 4 * DBL_EPSILON
355355
return diff > -epsilon && diff <= epsilon;
356356
}
357357

358+
//! Compare two floats (but allow some difference)
359+
//! @param a first float
360+
//! @param b second float
361+
//! @param epsilon maximum difference allowable between floats
362+
inline bool qgsFloatNear( float a, float b, float epsilon = 4 * FLT_EPSILON )
363+
{
364+
const float diff = a - b;
365+
return diff > -epsilon && diff <= epsilon;
366+
}
367+
358368
//! Compare two doubles using specified number of significant digits
359369
inline bool qgsDoubleNearSig( double a, double b, int significantDigits = 10 )
360370
{

src/core/raster/qgsrasterblock.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,27 @@ QString QgsRasterBlock::printValue( double value )
831831
return s;
832832
}
833833

834+
QString QgsRasterBlock::printValue( float value )
835+
{
836+
/*
837+
* IEEE 754 double has 6-9 significant digits. See printValue(double)
838+
*/
839+
840+
QString s;
841+
842+
for ( int i = 6; i <= 9; i++ )
843+
{
844+
s.setNum( value, 'g', i );
845+
if ( qgsFloatNear( s.toFloat(), value ) )
846+
{
847+
return s;
848+
}
849+
}
850+
// Should not happen
851+
QgsDebugMsg( "Cannot correctly parse printed value" );
852+
return s;
853+
}
854+
834855
void * QgsRasterBlock::convert( void *srcData, QGis::DataType srcDataType, QGis::DataType destDataType, qgssize size )
835856
{
836857
int destDataTypeSize = typeSize( destDataType );

src/core/raster/qgsrasterblock.h

+6
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,12 @@ class CORE_EXPORT QgsRasterBlock
286286
* @return string representing the value*/
287287
static QString printValue( double value );
288288

289+
/** \brief Print float value with all necessary significant digits.
290+
* It is ensured that conversion back to float gives the same number.
291+
* @param value the value to be printed
292+
* @return string representing the value*/
293+
static QString printValue( float value );
294+
289295
/** \brief Convert data to different type.
290296
* @param destDataType dest data type
291297
* @return true on success */

src/gui/qgsmaptoolidentify.cpp

+12-2
Original file line numberDiff line numberDiff line change
@@ -554,8 +554,18 @@ bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, Qg
554554
}
555555
else
556556
{
557-
double value = values.value( bandNo ).toDouble();
558-
valueString = QgsRasterBlock::printValue( value );
557+
QVariant value( values.value( bandNo ) );
558+
// The cast is legit. Quoting QT doc :
559+
// "Although this function is declared as returning QVariant::Type,
560+
// the return value should be interpreted as QMetaType::Type"
561+
if ( static_cast<QMetaType::Type>( value.type() ) == QMetaType::Float )
562+
{
563+
valueString = QgsRasterBlock::printValue( value.toFloat() );
564+
}
565+
else
566+
{
567+
valueString = QgsRasterBlock::printValue( value.toDouble() );
568+
}
559569
}
560570
attributes.insert( dprovider->generateBandName( bandNo ), valueString );
561571
}

src/providers/gdal/qgsgdalprovider.cpp

+14-4
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,12 @@ QgsGdalProvider::QgsGdalProvider( const QString &uri, bool update )
139139

140140
QgsGdalProviderBase::registerGdalDrivers();
141141

142-
// GDAL tends to open AAIGrid as Float32 which results in lost precision
143-
// and confusing values shown to users, force Float64
144-
CPLSetConfigOption( "AAIGRID_DATATYPE", "Float64" );
142+
if ( !CPLGetConfigOption( "AAIGRID_DATATYPE", nullptr ) )
143+
{
144+
// GDAL tends to open AAIGrid as Float32 which results in lost precision
145+
// and confusing values shown to users, force Float64
146+
CPLSetConfigOption( "AAIGRID_DATATYPE", "Float64" );
147+
}
145148

146149
// To get buildSupportedRasterFileFilter the provider is called with empty uri
147150
if ( uri.isEmpty() )
@@ -1068,7 +1071,14 @@ QgsRasterIdentifyResult QgsGdalProvider::identify( const QgsPoint & thePoint, Qg
10681071
}
10691072
else
10701073
{
1071-
results.insert( i, value );
1074+
if ( srcDataType( i ) == QGis::Float32 )
1075+
{
1076+
// Insert a float QVariant so that QgsMapToolIdentify::identifyRasterLayer()
1077+
// can print a string without an excessive precision
1078+
results.insert( i, static_cast<float>( value ) );
1079+
}
1080+
else
1081+
results.insert( i, value );
10721082
}
10731083
delete myBlock;
10741084
}

tests/src/app/testqgsmaptoolidentifyaction.cpp

+74
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <QtTest/QtTest>
1717
#include "qgsapplication.h"
1818
#include "qgsvectorlayer.h"
19+
#include "qgsrasterlayer.h"
1920
#include "qgsfeature.h"
2021
#include "qgsgeometry.h"
2122
#include "qgsvectordataprovider.h"
@@ -24,6 +25,8 @@
2425
#include "qgsunittypes.h"
2526
#include "qgsmaptoolidentifyaction.h"
2627

28+
#include "cpl_conv.h"
29+
2730
class TestQgsMapToolIdentifyAction : public QObject
2831
{
2932
Q_OBJECT
@@ -40,9 +43,13 @@ class TestQgsMapToolIdentifyAction : public QObject
4043
void lengthCalculation(); //test calculation of derived length attributes
4144
void perimeterCalculation(); //test calculation of derived perimeter attribute
4245
void areaCalculation(); //test calculation of derived area attribute
46+
void identifyRasterFloat32(); // test pixel identification and decimal precision
47+
void identifyRasterFloat64(); // test pixel identification and decimal precision
4348

4449
private:
4550
QgsMapCanvas* canvas;
51+
52+
QString testIdentifyRaster( QgsRasterLayer* layer, double xGeoref, double yGeoref );
4653
};
4754

4855
void TestQgsMapToolIdentifyAction::initTestCase()
@@ -241,6 +248,73 @@ void TestQgsMapToolIdentifyAction::areaCalculation()
241248
QVERIFY( qgsDoubleNear( area, 389.6117, 0.001 ) );
242249
}
243250

251+
QString TestQgsMapToolIdentifyAction::testIdentifyRaster( QgsRasterLayer* layer, double xGeoref, double yGeoref )
252+
{
253+
QScopedPointer< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
254+
QgsPoint mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
255+
QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer*>() << layer );
256+
if ( result.length() != 1 )
257+
return "";
258+
return result[0].mAttributes["Band 1"];
259+
}
260+
261+
void TestQgsMapToolIdentifyAction::identifyRasterFloat32()
262+
{
263+
//create a temporary layer
264+
QString raster = QString( TEST_DATA_DIR ) + "/raster/test.asc";
265+
266+
// By default the QgsRasterLayer forces AAIGRID_DATATYPE=Float64
267+
CPLSetConfigOption( "AAIGRID_DATATYPE", "Float32" );
268+
QScopedPointer< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) );
269+
CPLSetConfigOption( "AAIGRID_DATATYPE", nullptr );
270+
271+
QVERIFY( tempLayer->isValid() );
272+
273+
canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
274+
275+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 0.5, 0.5 ), QString( "-999.9" ) );
276+
277+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 1.5, 0.5 ), QString( "-999.987" ) );
278+
279+
// More than 6 significant digits : precision loss in Float32
280+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 2.5, 0.5 ), QString( "1.234568" ) ); // in .asc file : 1.2345678
281+
282+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 3.5, 0.5 ), QString( "123456" ) );
283+
284+
// More than 6 significant digits: no precision loss here for that particular value
285+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 4.5, 0.5 ), QString( "1234567" ) );
286+
287+
// More than 6 significant digits: no precision loss here for that particular value
288+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 5.5, 0.5 ), QString( "-999.9876" ) );
289+
290+
// More than 6 significant digits: no precision loss here
291+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 6.5, 0.5 ), QString( "1.234568" ) ); // in .asc file : 1.2345678901234
292+
}
293+
294+
void TestQgsMapToolIdentifyAction::identifyRasterFloat64()
295+
{
296+
//create a temporary layer
297+
QString raster = QString( TEST_DATA_DIR ) + "/raster/test.asc";
298+
QScopedPointer< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) );
299+
QVERIFY( tempLayer->isValid() );
300+
301+
canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
302+
303+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 0.5, 0.5 ), QString( "-999.9" ) );
304+
305+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 1.5, 0.5 ), QString( "-999.987" ) );
306+
307+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 2.5, 0.5 ), QString( "1.2345678" ) );
308+
309+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 3.5, 0.5 ), QString( "123456" ) );
310+
311+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 4.5, 0.5 ), QString( "1234567" ) );
312+
313+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 5.5, 0.5 ), QString( "-999.9876" ) );
314+
315+
QCOMPARE( testIdentifyRaster( tempLayer.data(), 6.5, 0.5 ), QString( "1.2345678901234" ) );
316+
}
317+
244318

245319
QTEST_MAIN( TestQgsMapToolIdentifyAction )
246320
#include "testqgsmaptoolidentifyaction.moc"

tests/testdata/raster/test.asc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ncols 7
2+
nrows 1
3+
xllcorner 0
4+
yllcorner 0
5+
cellsize 1
6+
-999.9 -999.987 1.2345678 123456 1234567 -999.9876 1.2345678901234

0 commit comments

Comments
 (0)