|
Quantum GIS API Documentation
master-693a1fe
|
00001 /*************************************************************************** 00002 qgsrasterface.cpp - Internal raster processing modules interface 00003 -------------------------------------- 00004 Date : Jun 21, 2012 00005 Copyright : (C) 2012 by Radim Blazek 00006 email : radim dot blazek at gmail dot com 00007 ***************************************************************************/ 00008 00009 /*************************************************************************** 00010 * * 00011 * This program is free software; you can redistribute it and/or modify * 00012 * it under the terms of the GNU General Public License as published by * 00013 * the Free Software Foundation; either version 2 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 ***************************************************************************/ 00017 00018 #include <limits> 00019 #include <typeinfo> 00020 00021 #include <QByteArray> 00022 #include <QTime> 00023 00024 #include <qmath.h> 00025 00026 #include "qgslogger.h" 00027 #include "qgsrasterbandstats.h" 00028 #include "qgsrasterhistogram.h" 00029 #include "qgsrasterinterface.h" 00030 #include "qgsrectangle.h" 00031 00032 QgsRasterInterface::QgsRasterInterface( QgsRasterInterface * input ) 00033 : mInput( input ) 00034 , mOn( true ) 00035 { 00036 } 00037 00038 QgsRasterInterface::~QgsRasterInterface() 00039 { 00040 } 00041 00042 void QgsRasterInterface::initStatistics( QgsRasterBandStats &theStatistics, 00043 int theBandNo, 00044 int theStats, 00045 const QgsRectangle & theExtent, 00046 int theSampleSize ) 00047 { 00048 QgsDebugMsg( QString( "theBandNo = %1 theSampleSize = %2" ).arg( theBandNo ).arg( theSampleSize ) ); 00049 00050 theStatistics.bandNumber = theBandNo; 00051 theStatistics.statsGathered = theStats; 00052 00053 QgsRectangle myExtent; 00054 if ( theExtent.isEmpty() ) 00055 { 00056 myExtent = extent(); 00057 } 00058 else 00059 { 00060 myExtent = extent().intersect( &theExtent ); 00061 } 00062 theStatistics.extent = myExtent; 00063 00064 if ( theSampleSize > 0 ) 00065 { 00066 // Calc resolution from theSampleSize 00067 double xRes, yRes; 00068 xRes = yRes = sqrt(( myExtent.width() * myExtent.height() ) / theSampleSize ); 00069 00070 // But limit by physical resolution 00071 if ( capabilities() & Size ) 00072 { 00073 double srcXRes = extent().width() / xSize(); 00074 double srcYRes = extent().height() / ySize(); 00075 if ( xRes < srcXRes ) xRes = srcXRes; 00076 if ( yRes < srcYRes ) yRes = srcYRes; 00077 } 00078 QgsDebugMsg( QString( "xRes = %1 yRes = %2" ).arg( xRes ).arg( yRes ) ); 00079 00080 theStatistics.width = static_cast <int>( myExtent.width() / xRes ); 00081 theStatistics.height = static_cast <int>( myExtent.height() / yRes ); 00082 } 00083 else 00084 { 00085 if ( capabilities() & Size ) 00086 { 00087 theStatistics.width = xSize(); 00088 theStatistics.height = ySize(); 00089 } 00090 else 00091 { 00092 theStatistics.width = 1000; 00093 theStatistics.height = 1000; 00094 } 00095 } 00096 QgsDebugMsg( QString( "theStatistics.width = %1 theStatistics.height = %2" ).arg( theStatistics.width ).arg( theStatistics.height ) ); 00097 } 00098 00099 bool QgsRasterInterface::hasStatistics( int theBandNo, 00100 int theStats, 00101 const QgsRectangle & theExtent, 00102 int theSampleSize ) 00103 { 00104 QgsDebugMsg( QString( "theBandNo = %1 theStats = %2 theSampleSize = %3" ).arg( theBandNo ).arg( theStats ).arg( theSampleSize ) ); 00105 if ( mStatistics.size() == 0 ) return false; 00106 00107 QgsRasterBandStats myRasterBandStats; 00108 initStatistics( myRasterBandStats, theBandNo, theStats, theExtent, theSampleSize ); 00109 00110 foreach ( QgsRasterBandStats stats, mStatistics ) 00111 { 00112 if ( stats.contains( myRasterBandStats ) ) 00113 { 00114 QgsDebugMsg( "Has cached statistics." ); 00115 return true; 00116 } 00117 } 00118 return false; 00119 } 00120 00121 QgsRasterBandStats QgsRasterInterface::bandStatistics( int theBandNo, 00122 int theStats, 00123 const QgsRectangle & theExtent, 00124 int theSampleSize ) 00125 { 00126 QgsDebugMsg( QString( "theBandNo = %1 theStats = %2 theSampleSize = %3" ).arg( theBandNo ).arg( theStats ).arg( theSampleSize ) ); 00127 00128 // TODO: null values set on raster layer!!! 00129 00130 QgsRasterBandStats myRasterBandStats; 00131 initStatistics( myRasterBandStats, theBandNo, theStats, theExtent, theSampleSize ); 00132 00133 foreach ( QgsRasterBandStats stats, mStatistics ) 00134 { 00135 if ( stats.contains( myRasterBandStats ) ) 00136 { 00137 QgsDebugMsg( "Using cached statistics." ); 00138 return stats; 00139 } 00140 } 00141 00142 QgsRectangle myExtent = myRasterBandStats.extent; 00143 int myWidth = myRasterBandStats.width; 00144 int myHeight = myRasterBandStats.height; 00145 00146 //int myDataType = dataType( theBandNo ); 00147 00148 int myXBlockSize = xBlockSize(); 00149 int myYBlockSize = yBlockSize(); 00150 if ( myXBlockSize == 0 ) // should not happen, but happens 00151 { 00152 myXBlockSize = 500; 00153 } 00154 if ( myYBlockSize == 0 ) // should not happen, but happens 00155 { 00156 myYBlockSize = 500; 00157 } 00158 00159 int myNXBlocks = ( myWidth + myXBlockSize - 1 ) / myXBlockSize; 00160 int myNYBlocks = ( myHeight + myYBlockSize - 1 ) / myYBlockSize; 00161 00162 double myXRes = myExtent.width() / myWidth; 00163 double myYRes = myExtent.height() / myHeight; 00164 // TODO: progress signals 00165 00166 // used by single pass stdev 00167 double myMean = 0; 00168 double mySumOfSquares = 0; 00169 00170 bool myFirstIterationFlag = true; 00171 for ( int myYBlock = 0; myYBlock < myNYBlocks; myYBlock++ ) 00172 { 00173 for ( int myXBlock = 0; myXBlock < myNXBlocks; myXBlock++ ) 00174 { 00175 QgsDebugMsg( QString( "myYBlock = %1 myXBlock = %2" ).arg( myYBlock ).arg( myXBlock ) ); 00176 int myBlockWidth = qMin( myXBlockSize, myWidth - myXBlock * myXBlockSize ); 00177 int myBlockHeight = qMin( myYBlockSize, myHeight - myYBlock * myYBlockSize ); 00178 00179 double xmin = myExtent.xMinimum() + myXBlock * myXBlockSize * myXRes; 00180 double xmax = xmin + myBlockWidth * myXRes; 00181 double ymin = myExtent.yMaximum() - myYBlock * myYBlockSize * myYRes; 00182 double ymax = ymin - myBlockHeight * myYRes; 00183 00184 QgsRectangle myPartExtent( xmin, ymin, xmax, ymax ); 00185 00186 QgsRasterBlock* blk = block( theBandNo, myPartExtent, myBlockWidth, myBlockHeight ); 00187 00188 // Collect the histogram counts. 00189 for ( size_t i = 0; i < (( size_t ) myBlockHeight ) * myBlockWidth; i++ ) 00190 { 00191 if ( blk->isNoData( i ) ) continue; // NULL 00192 00193 double myValue = blk->value( i ); 00194 00195 myRasterBandStats.sum += myValue; 00196 myRasterBandStats.elementCount++; 00197 00198 if ( myFirstIterationFlag ) 00199 { 00200 myFirstIterationFlag = false; 00201 myRasterBandStats.minimumValue = myValue; 00202 myRasterBandStats.maximumValue = myValue; 00203 } 00204 else 00205 { 00206 if ( myValue < myRasterBandStats.minimumValue ) 00207 { 00208 myRasterBandStats.minimumValue = myValue; 00209 } 00210 if ( myValue > myRasterBandStats.maximumValue ) 00211 { 00212 myRasterBandStats.maximumValue = myValue; 00213 } 00214 } 00215 00216 // Single pass stdev 00217 double myDelta = myValue - myMean; 00218 myMean += myDelta / myRasterBandStats.elementCount; 00219 mySumOfSquares += myDelta * ( myValue - myMean ); 00220 } 00221 delete blk; 00222 } 00223 } 00224 00225 myRasterBandStats.range = myRasterBandStats.maximumValue - myRasterBandStats.minimumValue; 00226 myRasterBandStats.mean = myRasterBandStats.sum / myRasterBandStats.elementCount; 00227 00228 myRasterBandStats.sumOfSquares = mySumOfSquares; // OK with single pass? 00229 00230 // stdDev may differ from GDAL stats, because GDAL is using naive single pass 00231 // algorithm which is more error prone (because of rounding errors) 00232 // Divide result by sample size - 1 and get square root to get stdev 00233 myRasterBandStats.stdDev = sqrt( mySumOfSquares / ( myRasterBandStats.elementCount - 1 ) ); 00234 00235 QgsDebugMsg( "************ STATS **************" ); 00236 QgsDebugMsg( QString( "MIN %1" ).arg( myRasterBandStats.minimumValue ) ); 00237 QgsDebugMsg( QString( "MAX %1" ).arg( myRasterBandStats.maximumValue ) ); 00238 QgsDebugMsg( QString( "RANGE %1" ).arg( myRasterBandStats.range ) ); 00239 QgsDebugMsg( QString( "MEAN %1" ).arg( myRasterBandStats.mean ) ); 00240 QgsDebugMsg( QString( "STDDEV %1" ).arg( myRasterBandStats.stdDev ) ); 00241 00242 myRasterBandStats.statsGathered = QgsRasterBandStats::All; 00243 mStatistics.append( myRasterBandStats ); 00244 00245 return myRasterBandStats; 00246 } 00247 00248 void QgsRasterInterface::initHistogram( QgsRasterHistogram &theHistogram, 00249 int theBandNo, 00250 int theBinCount, 00251 double theMinimum, double theMaximum, 00252 const QgsRectangle & theExtent, 00253 int theSampleSize, 00254 bool theIncludeOutOfRange ) 00255 { 00256 theHistogram.bandNumber = theBandNo; 00257 theHistogram.minimum = theMinimum; 00258 theHistogram.maximum = theMaximum; 00259 theHistogram.includeOutOfRange = theIncludeOutOfRange; 00260 00261 int mySrcDataType = srcDataType( theBandNo ); 00262 00263 if ( qIsNaN( theHistogram.minimum ) ) 00264 { 00265 // TODO: this was OK when stats/histogram were calced in provider, 00266 // but what TODO in other interfaces? Check for mInput for now. 00267 if ( !mInput && mySrcDataType == QGis::Byte ) 00268 { 00269 theHistogram.minimum = 0; // see histogram() for shift for rounding 00270 } 00271 else 00272 { 00273 // We need statistics -> avoid histogramDefaults in hasHistogram if possible 00274 // TODO: use approximated statistics if aproximated histogram is requested 00275 // (theSampleSize > 0) 00276 QgsRasterBandStats stats = bandStatistics( theBandNo, QgsRasterBandStats::Min, theExtent, theSampleSize ); 00277 theHistogram.minimum = stats.minimumValue; 00278 } 00279 } 00280 if ( qIsNaN( theHistogram.maximum ) ) 00281 { 00282 if ( !mInput && mySrcDataType == QGis::Byte ) 00283 { 00284 theHistogram.maximum = 255; 00285 } 00286 else 00287 { 00288 QgsRasterBandStats stats = bandStatistics( theBandNo, QgsRasterBandStats::Max, theExtent, theSampleSize ); 00289 theHistogram.maximum = stats.maximumValue; 00290 } 00291 } 00292 00293 QgsRectangle myExtent; 00294 if ( theExtent.isEmpty() ) 00295 { 00296 myExtent = extent(); 00297 } 00298 else 00299 { 00300 myExtent = extent().intersect( &theExtent ); 00301 } 00302 theHistogram.extent = myExtent; 00303 00304 if ( theSampleSize > 0 ) 00305 { 00306 // Calc resolution from theSampleSize 00307 double xRes, yRes; 00308 xRes = yRes = sqrt(( myExtent.width() * myExtent.height() ) / theSampleSize ); 00309 00310 // But limit by physical resolution 00311 if ( capabilities() & Size ) 00312 { 00313 double srcXRes = extent().width() / xSize(); 00314 double srcYRes = extent().height() / ySize(); 00315 if ( xRes < srcXRes ) xRes = srcXRes; 00316 if ( yRes < srcYRes ) yRes = srcYRes; 00317 } 00318 QgsDebugMsg( QString( "xRes = %1 yRes = %2" ).arg( xRes ).arg( yRes ) ); 00319 00320 theHistogram.width = static_cast <int>( myExtent.width() / xRes ); 00321 theHistogram.height = static_cast <int>( myExtent.height() / yRes ); 00322 } 00323 else 00324 { 00325 if ( capabilities() & Size ) 00326 { 00327 theHistogram.width = xSize(); 00328 theHistogram.height = ySize(); 00329 } 00330 else 00331 { 00332 theHistogram.width = 1000; 00333 theHistogram.height = 1000; 00334 } 00335 } 00336 QgsDebugMsg( QString( "theHistogram.width = %1 theHistogram.height = %2" ).arg( theHistogram.width ).arg( theHistogram.height ) ); 00337 00338 int myBinCount = theBinCount; 00339 if ( myBinCount == 0 ) 00340 { 00341 // TODO: this was OK when stats/histogram were calced in provider, 00342 // but what TODO in other interfaces? Check for mInput for now. 00343 if ( !mInput && mySrcDataType == QGis::Byte ) 00344 { 00345 myBinCount = 256; // Cannot store more values in byte 00346 } 00347 else 00348 { 00349 // There is no best default value, to display something reasonable in histogram chart, binCount should be small, OTOH, to get precise data for cumulative cut, the number should be big. Because it is easier to define fixed lower value for the chart, we calc optimum binCount for higher resolution (to avoid calculating that where histogram() is used. In any any case, it does not make sense to use more than width*height; 00350 myBinCount = theHistogram.width * theHistogram.height; 00351 if ( myBinCount > 1000 ) myBinCount = 1000; 00352 } 00353 } 00354 theHistogram.binCount = myBinCount; 00355 QgsDebugMsg( QString( "theHistogram.binCount = %1" ).arg( theHistogram.binCount ) ); 00356 } 00357 00358 00359 bool QgsRasterInterface::hasHistogram( int theBandNo, 00360 int theBinCount, 00361 double theMinimum, double theMaximum, 00362 const QgsRectangle & theExtent, 00363 int theSampleSize, 00364 bool theIncludeOutOfRange ) 00365 { 00366 QgsDebugMsg( QString( "theBandNo = %1 theBinCount = %2 theMinimum = %3 theMaximum = %4 theSampleSize = %5" ).arg( theBandNo ).arg( theBinCount ).arg( theMinimum ).arg( theMaximum ).arg( theSampleSize ) ); 00367 // histogramDefaults() needs statistics if theMinimum or theMaximum is NaN -> 00368 // do other checks which don't need statistics before histogramDefaults() 00369 if ( mHistograms.size() == 0 ) return false; 00370 00371 QgsRasterHistogram myHistogram; 00372 initHistogram( myHistogram, theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange ); 00373 00374 foreach ( QgsRasterHistogram histogram, mHistograms ) 00375 { 00376 if ( histogram == myHistogram ) 00377 { 00378 QgsDebugMsg( "Has cached histogram." ); 00379 return true; 00380 } 00381 } 00382 return false; 00383 } 00384 00385 QgsRasterHistogram QgsRasterInterface::histogram( int theBandNo, 00386 int theBinCount, 00387 double theMinimum, double theMaximum, 00388 const QgsRectangle & theExtent, 00389 int theSampleSize, 00390 bool theIncludeOutOfRange ) 00391 { 00392 QgsDebugMsg( QString( "theBandNo = %1 theBinCount = %2 theMinimum = %3 theMaximum = %4 theSampleSize = %5" ).arg( theBandNo ).arg( theBinCount ).arg( theMinimum ).arg( theMaximum ).arg( theSampleSize ) ); 00393 00394 QgsRasterHistogram myHistogram; 00395 initHistogram( myHistogram, theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange ); 00396 00397 // Find cached 00398 foreach ( QgsRasterHistogram histogram, mHistograms ) 00399 { 00400 if ( histogram == myHistogram ) 00401 { 00402 QgsDebugMsg( "Using cached histogram." ); 00403 return histogram; 00404 } 00405 } 00406 00407 int myBinCount = myHistogram.binCount; 00408 int myWidth = myHistogram.width; 00409 int myHeight = myHistogram.height; 00410 QgsRectangle myExtent = myHistogram.extent; 00411 myHistogram.histogramVector.resize( myBinCount ); 00412 00413 int myXBlockSize = xBlockSize(); 00414 int myYBlockSize = yBlockSize(); 00415 if ( myXBlockSize == 0 ) // should not happen, but happens 00416 { 00417 myXBlockSize = 500; 00418 } 00419 if ( myYBlockSize == 0 ) // should not happen, but happens 00420 { 00421 myYBlockSize = 500; 00422 } 00423 00424 int myNXBlocks = ( myWidth + myXBlockSize - 1 ) / myXBlockSize; 00425 int myNYBlocks = ( myHeight + myYBlockSize - 1 ) / myYBlockSize; 00426 00427 double myXRes = myExtent.width() / myWidth; 00428 double myYRes = myExtent.height() / myHeight; 00429 00430 double myMinimum = myHistogram.minimum; 00431 double myMaximum = myHistogram.maximum; 00432 00433 // To avoid rounding errors 00434 // TODO: check this 00435 double myerval = ( myMaximum - myMinimum ) / myHistogram.binCount; 00436 myMinimum -= 0.1 * myerval; 00437 myMaximum += 0.1 * myerval; 00438 00439 QgsDebugMsg( QString( "binCount = %1 myMinimum = %2 myMaximum = %3" ).arg( myHistogram.binCount ).arg( myMinimum ).arg( myMaximum ) ); 00440 00441 double myBinSize = ( myMaximum - myMinimum ) / myBinCount; 00442 00443 // TODO: progress signals 00444 for ( int myYBlock = 0; myYBlock < myNYBlocks; myYBlock++ ) 00445 { 00446 for ( int myXBlock = 0; myXBlock < myNXBlocks; myXBlock++ ) 00447 { 00448 int myBlockWidth = qMin( myXBlockSize, myWidth - myXBlock * myXBlockSize ); 00449 int myBlockHeight = qMin( myYBlockSize, myHeight - myYBlock * myYBlockSize ); 00450 00451 double xmin = myExtent.xMinimum() + myXBlock * myXBlockSize * myXRes; 00452 double xmax = xmin + myBlockWidth * myXRes; 00453 double ymin = myExtent.yMaximum() - myYBlock * myYBlockSize * myYRes; 00454 double ymax = ymin - myBlockHeight * myYRes; 00455 00456 QgsRectangle myPartExtent( xmin, ymin, xmax, ymax ); 00457 00458 QgsRasterBlock* blk = block( theBandNo, myPartExtent, myBlockWidth, myBlockHeight ); 00459 00460 // Collect the histogram counts. 00461 for ( size_t i = 0; i < (( size_t ) myBlockHeight ) * myBlockWidth; i++ ) 00462 { 00463 if ( blk->isNoData( i ) ) 00464 { 00465 continue; // NULL 00466 } 00467 double myValue = blk->value( i ); 00468 00469 int myBinIndex = static_cast <int>( qFloor(( myValue - myMinimum ) / myBinSize ) ) ; 00470 00471 if (( myBinIndex < 0 || myBinIndex > ( myBinCount - 1 ) ) && !theIncludeOutOfRange ) 00472 { 00473 continue; 00474 } 00475 if ( myBinIndex < 0 ) myBinIndex = 0; 00476 if ( myBinIndex > ( myBinCount - 1 ) ) myBinIndex = myBinCount - 1; 00477 00478 myHistogram.histogramVector[myBinIndex] += 1; 00479 myHistogram.nonNullCount++; 00480 } 00481 delete blk; 00482 } 00483 } 00484 00485 myHistogram.valid = true; 00486 mHistograms.append( myHistogram ); 00487 00488 #ifdef QGISDEBUG 00489 QString hist; 00490 for ( int i = 0; i < qMin( myHistogram.histogramVector.size(), 500 ); i++ ) 00491 { 00492 hist += QString::number( myHistogram.histogramVector.value( i ) ) + " "; 00493 } 00494 QgsDebugMsg( "Histogram (max first 500 bins): " + hist ); 00495 #endif 00496 00497 return myHistogram; 00498 } 00499 00500 void QgsRasterInterface::cumulativeCut( int theBandNo, 00501 double theLowerCount, double theUpperCount, 00502 double &theLowerValue, double &theUpperValue, 00503 const QgsRectangle & theExtent, 00504 int theSampleSize ) 00505 { 00506 QgsDebugMsg( QString( "theBandNo = %1 theLowerCount = %2 theUpperCount = %3 theSampleSize = %4" ).arg( theBandNo ).arg( theLowerCount ).arg( theUpperCount ).arg( theSampleSize ) ); 00507 00508 QgsRasterHistogram myHistogram = histogram( theBandNo, 0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), theExtent, theSampleSize ); 00509 00510 // Init to NaN is better than histogram min/max to catch errors 00511 theLowerValue = std::numeric_limits<double>::quiet_NaN(); 00512 theUpperValue = std::numeric_limits<double>::quiet_NaN(); 00513 00514 double myBinXStep = ( myHistogram.maximum - myHistogram.minimum ) / myHistogram.binCount; 00515 int myCount = 0; 00516 int myMinCount = ( int ) qRound( theLowerCount * myHistogram.nonNullCount ); 00517 int myMaxCount = ( int ) qRound( theUpperCount * myHistogram.nonNullCount ); 00518 bool myLowerFound = false; 00519 QgsDebugMsg( QString( "binCount = %1 minimum = %2 maximum = %3 myBinXStep = %4" ).arg( myHistogram.binCount ).arg( myHistogram.minimum ).arg( myHistogram.maximum ).arg( myBinXStep ) ); 00520 QgsDebugMsg( QString( "myMinCount = %1 myMaxCount = %2" ).arg( myMinCount ).arg( myMaxCount ) ); 00521 00522 for ( int myBin = 0; myBin < myHistogram.histogramVector.size(); myBin++ ) 00523 { 00524 int myBinValue = myHistogram.histogramVector.value( myBin ); 00525 myCount += myBinValue; 00526 if ( !myLowerFound && myCount > myMinCount ) 00527 { 00528 theLowerValue = myHistogram.minimum + myBin * myBinXStep; 00529 myLowerFound = true; 00530 } 00531 if ( myCount >= myMaxCount ) 00532 { 00533 theUpperValue = myHistogram.minimum + myBin * myBinXStep; 00534 break; 00535 } 00536 } 00537 } 00538 00539 QString QgsRasterInterface::capabilitiesString() const 00540 { 00541 QStringList abilitiesList; 00542 00543 int abilities = capabilities(); 00544 00545 // Not all all capabilities are here (Size, IdentifyValue, IdentifyText, 00546 // IdentifyHtml, IdentifyFeature) because those are quite technical and probably 00547 // would be confusing for users 00548 00549 if ( abilities & QgsRasterInterface::Identify ) 00550 { 00551 abilitiesList += tr( "Identify" ); 00552 } 00553 00554 if ( abilities & QgsRasterInterface::Create ) 00555 { 00556 abilitiesList += tr( "Create Datasources" ); 00557 } 00558 00559 if ( abilities & QgsRasterInterface::Remove ) 00560 { 00561 abilitiesList += tr( "Remove Datasources" ); 00562 } 00563 00564 if ( abilities & QgsRasterInterface::BuildPyramids ) 00565 { 00566 abilitiesList += tr( "Build Pyramids" ); 00567 } 00568 00569 QgsDebugMsg( "Capability: " + abilitiesList.join( ", " ) ); 00570 00571 return abilitiesList.join( ", " ); 00572 }