QGIS API Documentation  2.10.1-Pisa
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsrasterprojector.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrasterprojector.cpp - Raster projector
3  --------------------------------------
4  Date : Jan 16, 2011
5  Copyright : (C) 2005 by Radim Blazek
6  email : radim dot blazek 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 #include <algorithm>
18 
19 #include "qgsrasterdataprovider.h"
20 #include "qgscrscache.h"
21 #include "qgslogger.h"
22 #include "qgsrasterprojector.h"
23 #include "qgscoordinatetransform.h"
24 
28  int theSrcDatumTransform,
29  int theDestDatumTransform,
30  QgsRectangle theDestExtent,
31  int theDestRows, int theDestCols,
32  double theMaxSrcXRes, double theMaxSrcYRes,
33  QgsRectangle theExtent )
34  : QgsRasterInterface( 0 )
35  , mSrcCRS( theSrcCRS )
36  , mDestCRS( theDestCRS )
37  , mSrcDatumTransform( theSrcDatumTransform )
38  , mDestDatumTransform( theDestDatumTransform )
39  , mDestExtent( theDestExtent )
40  , mExtent( theExtent )
41  , mDestRows( theDestRows ), mDestCols( theDestCols )
42  , pHelperTop( 0 ), pHelperBottom( 0 )
43  , mMaxSrcXRes( theMaxSrcXRes ), mMaxSrcYRes( theMaxSrcYRes )
44  , mPrecision( Approximate )
45  , mApproximate( true )
46 {
47  QgsDebugMsg( "Entered" );
48  QgsDebugMsg( "theDestExtent = " + theDestExtent.toString() );
49 
50  calc();
51 }
52 
56  QgsRectangle theDestExtent,
57  int theDestRows, int theDestCols,
58  double theMaxSrcXRes, double theMaxSrcYRes,
59  QgsRectangle theExtent )
60  : QgsRasterInterface( 0 )
61  , mSrcCRS( theSrcCRS )
62  , mDestCRS( theDestCRS )
63  , mSrcDatumTransform( -1 )
64  , mDestDatumTransform( -1 )
65  , mDestExtent( theDestExtent )
66  , mExtent( theExtent )
67  , mDestRows( theDestRows ), mDestCols( theDestCols )
68  , pHelperTop( 0 ), pHelperBottom( 0 )
69  , mMaxSrcXRes( theMaxSrcXRes ), mMaxSrcYRes( theMaxSrcYRes )
70  , mPrecision( Approximate )
71  , mApproximate( false )
72 {
73  QgsDebugMsg( "Entered" );
74  QgsDebugMsg( "theDestExtent = " + theDestExtent.toString() );
75 
76  calc();
77 }
78 
82  double theMaxSrcXRes, double theMaxSrcYRes,
83  QgsRectangle theExtent )
84  : QgsRasterInterface( 0 )
85  , mSrcCRS( theSrcCRS )
86  , mDestCRS( theDestCRS )
87  , mSrcDatumTransform( -1 )
88  , mDestDatumTransform( -1 )
89  , mExtent( theExtent )
90  , mDestRows( 0 )
91  , mDestCols( 0 )
92  , mDestXRes( 0.0 )
93  , mDestYRes( 0.0 )
94  , mSrcRows( 0 )
95  , mSrcCols( 0 )
96  , mSrcXRes( 0.0 )
97  , mSrcYRes( 0.0 )
98  , mDestRowsPerMatrixRow( 0.0 )
99  , mDestColsPerMatrixCol( 0.0 )
100  , pHelperTop( 0 ), pHelperBottom( 0 )
101  , mHelperTopRow( 0 )
102  , mCPCols( 0 )
103  , mCPRows( 0 )
104  , mSqrTolerance( 0.0 )
105  , mMaxSrcXRes( theMaxSrcXRes )
106  , mMaxSrcYRes( theMaxSrcYRes )
107  , mPrecision( Approximate )
108  , mApproximate( false )
109 {
110  QgsDebugMsg( "Entered" );
111 }
112 
114  : QgsRasterInterface( 0 )
115  , mSrcDatumTransform( -1 )
116  , mDestDatumTransform( -1 )
117  , mDestRows( 0 )
118  , mDestCols( 0 )
119  , mDestXRes( 0.0 )
120  , mDestYRes( 0.0 )
121  , mSrcRows( 0 )
122  , mSrcCols( 0 )
123  , mSrcXRes( 0.0 )
124  , mSrcYRes( 0.0 )
125  , mDestRowsPerMatrixRow( 0.0 )
126  , mDestColsPerMatrixCol( 0.0 )
127  , pHelperTop( 0 )
128  , pHelperBottom( 0 )
129  , mHelperTopRow( 0 )
130  , mCPCols( 0 )
131  , mCPRows( 0 )
132  , mSqrTolerance( 0.0 )
133  , mMaxSrcXRes( 0 )
134  , mMaxSrcYRes( 0 )
135  , mPrecision( Approximate )
136  , mApproximate( false )
137 {
138  QgsDebugMsg( "Entered" );
139 }
140 
142  : QgsRasterInterface( 0 )
143  , pHelperTop( NULL )
144  , pHelperBottom( NULL )
145  , mHelperTopRow( 0 )
146  , mCPCols( 0 )
147  , mCPRows( 0 )
148  , mSqrTolerance( 0 )
149  , mApproximate( true )
150 {
151  mSrcCRS = projector.mSrcCRS;
152  mDestCRS = projector.mDestCRS;
153  mSrcDatumTransform = projector.mSrcDatumTransform;
154  mDestDatumTransform = projector.mDestDatumTransform;
155  mMaxSrcXRes = projector.mMaxSrcXRes;
156  mMaxSrcYRes = projector.mMaxSrcYRes;
157  mExtent = projector.mExtent;
158  mDestRows = projector.mDestRows;
159  mDestCols = projector.mDestCols;
160  mDestXRes = projector.mDestXRes;
161  mDestYRes = projector.mDestYRes;
162  mSrcRows = projector.mSrcRows;
163  mSrcCols = projector.mSrcCols;
164  mSrcXRes = projector.mSrcXRes;
165  mSrcYRes = projector.mSrcYRes;
166  mDestRowsPerMatrixRow = projector.mDestRowsPerMatrixRow;
167  mDestColsPerMatrixCol = projector.mDestColsPerMatrixCol;
168  mPrecision = projector.mPrecision;
169 }
170 
172 {
173  if ( &projector != this )
174  {
175  mSrcCRS = projector.mSrcCRS;
176  mDestCRS = projector.mDestCRS;
177  mSrcDatumTransform = projector.mSrcDatumTransform;
178  mDestDatumTransform = projector.mDestDatumTransform;
179  mMaxSrcXRes = projector.mMaxSrcXRes;
180  mMaxSrcYRes = projector.mMaxSrcYRes;
181  mExtent = projector.mExtent;
182  mPrecision = projector.mPrecision;
183  }
184  return *this;
185 }
186 
188 {
189  QgsDebugMsg( "Entered" );
190  QgsRasterProjector * projector = new QgsRasterProjector( mSrcCRS, mDestCRS, mMaxSrcXRes, mMaxSrcYRes, mExtent );
191  projector->mSrcDatumTransform = mSrcDatumTransform;
192  projector->mDestDatumTransform = mDestDatumTransform;
193  projector->mPrecision = mPrecision;
194  return projector;
195 }
196 
198 {
199  delete[] pHelperTop;
200  delete[] pHelperBottom;
201 }
202 
204 {
205  if ( mInput ) return mInput->bandCount();
206 
207  return 0;
208 }
209 
211 {
212  if ( mInput ) return mInput->dataType( bandNo );
213 
214  return QGis::UnknownDataType;
215 }
216 
217 void QgsRasterProjector::setCRS( const QgsCoordinateReferenceSystem & theSrcCRS, const QgsCoordinateReferenceSystem & theDestCRS, int srcDatumTransform, int destDatumTransform )
218 {
219  mSrcCRS = theSrcCRS;
220  mDestCRS = theDestCRS;
221  mSrcDatumTransform = srcDatumTransform;
222  mDestDatumTransform = destDatumTransform;
223 }
224 
225 void QgsRasterProjector::calc()
226 {
227  QgsDebugMsg( "Entered" );
228  mCPMatrix.clear();
229  mCPLegalMatrix.clear();
230  delete[] pHelperTop;
231  pHelperTop = 0;
232  delete[] pHelperBottom;
233  pHelperBottom = 0;
234 
235  // Get max source resolution and extent if possible
236  mMaxSrcXRes = 0;
237  mMaxSrcYRes = 0;
238  if ( mInput )
239  {
240  QgsRasterDataProvider *provider = dynamic_cast<QgsRasterDataProvider*>( mInput->srcInput() );
241  if ( provider )
242  {
243  if ( provider->capabilities() & QgsRasterDataProvider::Size )
244  {
245  mMaxSrcXRes = provider->extent().width() / provider->xSize();
246  mMaxSrcYRes = provider->extent().height() / provider->ySize();
247  }
248  // Get source extent
249  if ( mExtent.isEmpty() )
250  {
251  mExtent = provider->extent();
252  }
253  }
254  }
255 
256  mDestXRes = mDestExtent.width() / ( mDestCols );
257  mDestYRes = mDestExtent.height() / ( mDestRows );
258 
259  // Calculate tolerance
260  // TODO: Think it over better
261  // Note: we are checking on matrix each even point, that means that the real error
262  // in that moment is approximately half size
263  double myDestRes = mDestXRes < mDestYRes ? mDestXRes : mDestYRes;
264  mSqrTolerance = myDestRes * myDestRes;
265 
266  const QgsCoordinateTransform* inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform );
267 
268  if ( mPrecision == Approximate )
269  {
270  // Initialize the matrix by corners and middle points
271  mCPCols = mCPRows = 3;
272  mApproximate = true;
273  for ( int i = 0; i < mCPRows; i++ )
274  {
275  QList<QgsPoint> myRow;
276  myRow.append( QgsPoint() );
277  myRow.append( QgsPoint() );
278  myRow.append( QgsPoint() );
279  mCPMatrix.insert( i, myRow );
280  // And the legal points
281  QList<bool> myLegalRow;
282  myLegalRow.append( bool( false ) );
283  myLegalRow.append( bool( false ) );
284  myLegalRow.append( bool( false ) );
285  mCPLegalMatrix.insert( i, myLegalRow );
286  }
287  for ( int i = 0; i < mCPRows; i++ )
288  {
289  calcRow( i, inverseCt );
290  }
291 
292  while ( true )
293  {
294  bool myColsOK = checkCols( inverseCt );
295  if ( !myColsOK )
296  {
297  insertRows( inverseCt );
298  }
299  bool myRowsOK = checkRows( inverseCt );
300  if ( !myRowsOK )
301  {
302  insertCols( inverseCt );
303  }
304  if ( myColsOK && myRowsOK )
305  {
306  QgsDebugMsg( "CP matrix within tolerance" );
307  break;
308  }
309  // What is the maximum reasonable size of transformatio matrix?
310  // TODO: consider better when to break - ratio
311  if ( mCPRows * mCPCols > 0.25 * mDestRows * mDestCols )
312  {
313  QgsDebugMsg( "Too large CP matrix" );
314  mApproximate = false;
315  break;
316  }
317  }
318  QgsDebugMsg( QString( "CPMatrix size: mCPRows = %1 mCPCols = %2" ).arg( mCPRows ).arg( mCPCols ) );
319  mDestRowsPerMatrixRow = ( float )mDestRows / ( mCPRows - 1 );
320  mDestColsPerMatrixCol = ( float )mDestCols / ( mCPCols - 1 );
321 
322  QgsDebugMsgLevel( "CPMatrix:", 5 );
323  QgsDebugMsgLevel( cpToString(), 5 );
324 
325  // init helper points
326  pHelperTop = new QgsPoint[mDestCols];
327  pHelperBottom = new QgsPoint[mDestCols];
328  calcHelper( 0, pHelperTop );
329  calcHelper( 1, pHelperBottom );
330  mHelperTopRow = 0;
331  }
332  else
333  {
334  mApproximate = false;
335  }
336  // Calculate source dimensions
337  calcSrcExtent();
338  calcSrcRowsCols();
339  mSrcYRes = mSrcExtent.height() / mSrcRows;
340  mSrcXRes = mSrcExtent.width() / mSrcCols;
341 }
342 
343 void QgsRasterProjector::calcSrcExtent()
344 {
345  /* Run around the mCPMatrix and find source extent */
346  // Attention, source limits are not necessarily on destination edges, e.g.
347  // for destination EPSG:32661 Polar Stereographic and source EPSG:4326,
348  // the maximum y may be in the middle of destination extent
349  // TODO: How to find extent exactly and quickly?
350  // For now, we runt through all matrix
351  if ( mApproximate )
352  {
353  QgsPoint myPoint = mCPMatrix[0][0];
354  mSrcExtent = QgsRectangle( myPoint.x(), myPoint.y(), myPoint.x(), myPoint.y() );
355  for ( int i = 0; i < mCPRows; i++ )
356  {
357  for ( int j = 0; j < mCPCols ; j++ )
358  {
359  myPoint = mCPMatrix[i][j];
360  if ( mCPLegalMatrix[i][j] )
361  {
362  mSrcExtent.combineExtentWith( myPoint.x(), myPoint.y() );
363  }
364  }
365  }
366  // Expand a bit to avoid possible approx coords falling out because of representation error?
367  }
368  else
369  {
370  const QgsCoordinateTransform* inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform );
371  mSrcExtent = inverseCt->transformBoundingBox( mDestExtent );
372  }
373 
374  // Combine with maximum source extent
375  mSrcExtent = mSrcExtent.intersect( &mExtent );
376 
377  // If mMaxSrcXRes, mMaxSrcYRes are defined (fixed src resolution)
378  // align extent to src resolution to avoid jumping of reprojected pixels
379  // when shifting resampled grid.
380  // Important especially if we are over mMaxSrcXRes, mMaxSrcYRes limits
381  // Note however, that preceding filters (like resampler) may read data
382  // on different resolution.
383 
384  QgsDebugMsg( "mSrcExtent = " + mSrcExtent.toString() );
385  QgsDebugMsg( "mExtent = " + mExtent.toString() );
386  if ( !mExtent.isEmpty() )
387  {
388  if ( mMaxSrcXRes > 0 )
389  {
390  // with floor/ceil it should work correctly also for mSrcExtent.xMinimum() < mExtent.xMinimum()
391  double col = floor(( mSrcExtent.xMinimum() - mExtent.xMinimum() ) / mMaxSrcXRes );
392  double x = mExtent.xMinimum() + col * mMaxSrcXRes;
393  mSrcExtent.setXMinimum( x );
394 
395  col = ceil(( mSrcExtent.xMaximum() - mExtent.xMinimum() ) / mMaxSrcXRes );
396  x = mExtent.xMinimum() + col * mMaxSrcXRes;
397  mSrcExtent.setXMaximum( x );
398  }
399  if ( mMaxSrcYRes > 0 )
400  {
401  double row = floor(( mExtent.yMaximum() - mSrcExtent.yMaximum() ) / mMaxSrcYRes );
402  double y = mExtent.yMaximum() - row * mMaxSrcYRes;
403  mSrcExtent.setYMaximum( y );
404 
405  row = ceil(( mExtent.yMaximum() - mSrcExtent.yMinimum() ) / mMaxSrcYRes );
406  y = mExtent.yMaximum() - row * mMaxSrcYRes;
407  mSrcExtent.setYMinimum( y );
408  }
409  }
410  QgsDebugMsg( "mSrcExtent = " + mSrcExtent.toString() );
411 }
412 
413 QString QgsRasterProjector::cpToString()
414 {
415  QString myString;
416  for ( int i = 0; i < mCPRows; i++ )
417  {
418  if ( i > 0 )
419  myString += "\n";
420  for ( int j = 0; j < mCPCols; j++ )
421  {
422  if ( j > 0 )
423  myString += " ";
424  QgsPoint myPoint = mCPMatrix[i][j];
425  if ( mCPLegalMatrix[i][j] )
426  {
427  myString += myPoint.toString();
428  }
429  else
430  {
431  myString += "(-,-)";
432  }
433  }
434  }
435  return myString;
436 }
437 
438 void QgsRasterProjector::calcSrcRowsCols()
439 {
440  // Wee need to calculate minimum cell size in the source
441  // TODO: Think it over better, what is the right source resolution?
442  // Taking distances between cell centers projected to source along source
443  // axis would result in very high resolution
444  // TODO: different resolution for rows and cols ?
445 
446  double myMinSize = std::numeric_limits<double>::max();
447 
448  if ( mApproximate )
449  {
450  // For now, we take cell sizes projected to source but not to source axes
451  double myDestColsPerMatrixCell = ( double )mDestCols / mCPCols;
452  double myDestRowsPerMatrixCell = ( double )mDestRows / mCPRows;
453  QgsDebugMsg( QString( "myDestColsPerMatrixCell = %1 myDestRowsPerMatrixCell = %2" ).arg( myDestColsPerMatrixCell ).arg( myDestRowsPerMatrixCell ) );
454  for ( int i = 0; i < mCPRows - 1; i++ )
455  {
456  for ( int j = 0; j < mCPCols - 1; j++ )
457  {
458  QgsPoint myPointA = mCPMatrix[i][j];
459  QgsPoint myPointB = mCPMatrix[i][j+1];
460  QgsPoint myPointC = mCPMatrix[i+1][j];
461  if ( mCPLegalMatrix[i][j] && mCPLegalMatrix[i][j+1] && mCPLegalMatrix[i+1][j] )
462  {
463  double mySize = sqrt( myPointA.sqrDist( myPointB ) ) / myDestColsPerMatrixCell;
464  if ( mySize < myMinSize )
465  myMinSize = mySize;
466 
467  mySize = sqrt( myPointA.sqrDist( myPointC ) ) / myDestRowsPerMatrixCell;
468  if ( mySize < myMinSize )
469  myMinSize = mySize;
470  }
471  }
472  }
473  }
474  else
475  {
476  // take highest from corners, points in in the middle of corners and center (3 x 3 )
477  const QgsCoordinateTransform* inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform );
478  //double
479  QgsRectangle srcExtent;
480  int srcXSize, srcYSize;
481  if ( extentSize( inverseCt, mDestExtent, mDestCols, mDestRows, srcExtent, srcXSize, srcYSize ) )
482  {
483  double srcXRes = srcExtent.width() / srcXSize;
484  double srcYRes = srcExtent.height() / srcYSize;
485  myMinSize = std::min( srcXRes, srcYRes );
486  }
487  else
488  {
489  QgsDebugMsg( "Cannot get src extent/size" );
490  }
491  }
492 
493  // Make it a bit higher resolution
494  // TODO: find the best coefficient, attention, increasing resolution for WMS
495  // is changing WMS content
496  myMinSize *= 0.75;
497 
498  QgsDebugMsg( QString( "mMaxSrcXRes = %1 mMaxSrcYRes = %2" ).arg( mMaxSrcXRes ).arg( mMaxSrcYRes ) );
499  // mMaxSrcXRes, mMaxSrcYRes may be 0 - no limit (WMS)
500  double myMinXSize = mMaxSrcXRes > myMinSize ? mMaxSrcXRes : myMinSize;
501  double myMinYSize = mMaxSrcYRes > myMinSize ? mMaxSrcYRes : myMinSize;
502  QgsDebugMsg( QString( "myMinXSize = %1 myMinYSize = %2" ).arg( myMinXSize ).arg( myMinYSize ) );
503  QgsDebugMsg( QString( "mSrcExtent.width = %1 mSrcExtent.height = %2" ).arg( mSrcExtent.width() ).arg( mSrcExtent.height() ) );
504 
505  // we have to round to keep alignment set in calcSrcExtent
506  mSrcRows = ( int ) qRound( mSrcExtent.height() / myMinYSize );
507  mSrcCols = ( int ) qRound( mSrcExtent.width() / myMinXSize );
508 
509  QgsDebugMsg( QString( "mSrcRows = %1 mSrcCols = %2" ).arg( mSrcRows ).arg( mSrcCols ) );
510 }
511 
512 
513 inline void QgsRasterProjector::destPointOnCPMatrix( int theRow, int theCol, double *theX, double *theY )
514 {
515  *theX = mDestExtent.xMinimum() + theCol * mDestExtent.width() / ( mCPCols - 1 );
516  *theY = mDestExtent.yMaximum() - theRow * mDestExtent.height() / ( mCPRows - 1 );
517 }
518 
519 inline int QgsRasterProjector::matrixRow( int theDestRow )
520 {
521  return ( int )( floor(( theDestRow + 0.5 ) / mDestRowsPerMatrixRow ) );
522 }
523 inline int QgsRasterProjector::matrixCol( int theDestCol )
524 {
525  return ( int )( floor(( theDestCol + 0.5 ) / mDestColsPerMatrixCol ) );
526 }
527 
528 QgsPoint QgsRasterProjector::srcPoint( int theDestRow, int theCol )
529 {
530  Q_UNUSED( theDestRow );
531  Q_UNUSED( theCol );
532  return QgsPoint();
533 }
534 
535 void QgsRasterProjector::calcHelper( int theMatrixRow, QgsPoint *thePoints )
536 {
537  // TODO?: should we also precalc dest cell center coordinates for x and y?
538  for ( int myDestCol = 0; myDestCol < mDestCols; myDestCol++ )
539  {
540  double myDestX = mDestExtent.xMinimum() + ( myDestCol + 0.5 ) * mDestXRes;
541 
542  int myMatrixCol = matrixCol( myDestCol );
543 
544  double myDestXMin, myDestYMin, myDestXMax, myDestYMax;
545 
546  destPointOnCPMatrix( theMatrixRow, myMatrixCol, &myDestXMin, &myDestYMin );
547  destPointOnCPMatrix( theMatrixRow, myMatrixCol + 1, &myDestXMax, &myDestYMax );
548 
549  double xfrac = ( myDestX - myDestXMin ) / ( myDestXMax - myDestXMin );
550 
551  QgsPoint &mySrcPoint0 = mCPMatrix[theMatrixRow][myMatrixCol];
552  QgsPoint &mySrcPoint1 = mCPMatrix[theMatrixRow][myMatrixCol+1];
553  double s = mySrcPoint0.x() + ( mySrcPoint1.x() - mySrcPoint0.x() ) * xfrac;
554  double t = mySrcPoint0.y() + ( mySrcPoint1.y() - mySrcPoint0.y() ) * xfrac;
555 
556  thePoints[myDestCol].setX( s );
557  thePoints[myDestCol].setY( t );
558  }
559 }
560 void QgsRasterProjector::nextHelper()
561 {
562  // We just switch pHelperTop and pHelperBottom, memory is not lost
563  QgsPoint *tmp;
564  tmp = pHelperTop;
565  pHelperTop = pHelperBottom;
566  pHelperBottom = tmp;
567  calcHelper( mHelperTopRow + 2, pHelperBottom );
568  mHelperTopRow++;
569 }
570 
571 bool QgsRasterProjector::srcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform* ct )
572 {
573  if ( mApproximate )
574  {
575  return approximateSrcRowCol( theDestRow, theDestCol, theSrcRow, theSrcCol );
576  }
577  else
578  {
579  return preciseSrcRowCol( theDestRow, theDestCol, theSrcRow, theSrcCol, ct );
580  }
581 }
582 
583 bool QgsRasterProjector::preciseSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol, const QgsCoordinateTransform* ct )
584 {
585 #ifdef QGISDEBUG
586  QgsDebugMsgLevel( QString( "theDestRow = %1" ).arg( theDestRow ), 5 );
587  QgsDebugMsgLevel( QString( "theDestRow = %1 mDestExtent.yMaximum() = %2 mDestYRes = %3" ).arg( theDestRow ).arg( mDestExtent.yMaximum() ).arg( mDestYRes ), 5 );
588 #endif
589 
590  // Get coordinate of center of destination cell
591  double x = mDestExtent.xMinimum() + ( theDestCol + 0.5 ) * mDestXRes;
592  double y = mDestExtent.yMaximum() - ( theDestRow + 0.5 ) * mDestYRes;
593  double z = 0;
594 
595 #ifdef QGISDEBUG
596  QgsDebugMsgLevel( QString( "x = %1 y = %2" ).arg( x ).arg( y ), 5 );
597 #endif
598 
599  if ( ct )
600  {
601  ct->transformInPlace( x, y, z );
602  }
603 
604 #ifdef QGISDEBUG
605  QgsDebugMsgLevel( QString( "x = %1 y = %2" ).arg( x ).arg( y ), 5 );
606 #endif
607 
608  if ( !mExtent.contains( QgsPoint( x, y ) ) )
609  {
610  return false;
611  }
612  // Get source row col
613  *theSrcRow = ( int ) floor(( mSrcExtent.yMaximum() - y ) / mSrcYRes );
614  *theSrcCol = ( int ) floor(( x - mSrcExtent.xMinimum() ) / mSrcXRes );
615 #ifdef QGISDEBUG
616  QgsDebugMsgLevel( QString( "mSrcExtent.yMinimum() = %1 mSrcExtent.yMaximum() = %2 mSrcYRes = %3" ).arg( mSrcExtent.yMinimum() ).arg( mSrcExtent.yMaximum() ).arg( mSrcYRes ), 5 );
617  QgsDebugMsgLevel( QString( "theSrcRow = %1 theSrcCol = %2" ).arg( *theSrcRow ).arg( *theSrcCol ), 5 );
618 #endif
619 
620  // With epsg 32661 (Polar Stereographic) it was happening that *theSrcCol == mSrcCols
621  // For now silently correct limits to avoid crashes
622  // TODO: review
623  // should not happen
624  if ( *theSrcRow >= mSrcRows ) return false;
625  if ( *theSrcRow < 0 ) return false;
626  if ( *theSrcCol >= mSrcCols ) return false;
627  if ( *theSrcCol < 0 ) return false;
628 
629  return true;
630 }
631 
632 bool QgsRasterProjector::approximateSrcRowCol( int theDestRow, int theDestCol, int *theSrcRow, int *theSrcCol )
633 {
634  int myMatrixRow = matrixRow( theDestRow );
635  int myMatrixCol = matrixCol( theDestCol );
636 
637  if ( myMatrixRow > mHelperTopRow )
638  {
639  // TODO: make it more robust (for random, not sequential reading)
640  nextHelper();
641  }
642 
643  double myDestY = mDestExtent.yMaximum() - ( theDestRow + 0.5 ) * mDestYRes;
644 
645  // See the schema in javax.media.jai.WarpGrid doc (but up side down)
646  // TODO: use some kind of cache of values which can be reused
647  double myDestXMin, myDestYMin, myDestXMax, myDestYMax;
648 
649  destPointOnCPMatrix( myMatrixRow + 1, myMatrixCol, &myDestXMin, &myDestYMin );
650  destPointOnCPMatrix( myMatrixRow, myMatrixCol + 1, &myDestXMax, &myDestYMax );
651 
652  double yfrac = ( myDestY - myDestYMin ) / ( myDestYMax - myDestYMin );
653 
654  QgsPoint &myTop = pHelperTop[theDestCol];
655  QgsPoint &myBot = pHelperBottom[theDestCol];
656 
657  // Warning: this is very SLOW compared to the following code!:
658  //double mySrcX = myBot.x() + (myTop.x() - myBot.x()) * yfrac;
659  //double mySrcY = myBot.y() + (myTop.y() - myBot.y()) * yfrac;
660 
661  double tx = myTop.x();
662  double ty = myTop.y();
663  double bx = myBot.x();
664  double by = myBot.y();
665  double mySrcX = bx + ( tx - bx ) * yfrac;
666  double mySrcY = by + ( ty - by ) * yfrac;
667 
668  if ( !mExtent.contains( QgsPoint( mySrcX, mySrcY ) ) )
669  {
670  return false;
671  }
672 
673  // TODO: check again cell selection (coor is in the middle)
674 
675  *theSrcRow = ( int ) floor(( mSrcExtent.yMaximum() - mySrcY ) / mSrcYRes );
676  *theSrcCol = ( int ) floor(( mySrcX - mSrcExtent.xMinimum() ) / mSrcXRes );
677 
678  // For now silently correct limits to avoid crashes
679  // TODO: review
680  // should not happen
681  if ( *theSrcRow >= mSrcRows ) return false;
682  if ( *theSrcRow < 0 ) return false;
683  if ( *theSrcCol >= mSrcCols ) return false;
684  if ( *theSrcCol < 0 ) return false;
685 
686  return true;
687 }
688 
689 void QgsRasterProjector::insertRows( const QgsCoordinateTransform* ct )
690 {
691  for ( int r = 0; r < mCPRows - 1; r++ )
692  {
693  QList<QgsPoint> myRow;
694  QList<bool> myLegalRow;
695  for ( int c = 0; c < mCPCols; c++ )
696  {
697  myRow.append( QgsPoint() );
698  myLegalRow.append( false );
699  }
700  QgsDebugMsgLevel( QString( "insert new row at %1" ).arg( 1 + r*2 ), 3 );
701  mCPMatrix.insert( 1 + r*2, myRow );
702  mCPLegalMatrix.insert( 1 + r*2, myLegalRow );
703  }
704  mCPRows += mCPRows - 1;
705  for ( int r = 1; r < mCPRows - 1; r += 2 )
706  {
707  calcRow( r, ct );
708  }
709 }
710 
711 void QgsRasterProjector::insertCols( const QgsCoordinateTransform* ct )
712 {
713  for ( int r = 0; r < mCPRows; r++ )
714  {
715  QList<QgsPoint> myRow;
716  QList<bool> myLegalRow;
717  for ( int c = 0; c < mCPCols - 1; c++ )
718  {
719  mCPMatrix[r].insert( 1 + c*2, QgsPoint() );
720  mCPLegalMatrix[r].insert( 1 + c*2, false );
721  }
722  }
723  mCPCols += mCPCols - 1;
724  for ( int c = 1; c < mCPCols - 1; c += 2 )
725  {
726  calcCol( c, ct );
727  }
728 
729 }
730 
731 void QgsRasterProjector::calcCP( int theRow, int theCol, const QgsCoordinateTransform* ct )
732 {
733  double myDestX, myDestY;
734  destPointOnCPMatrix( theRow, theCol, &myDestX, &myDestY );
735  QgsPoint myDestPoint( myDestX, myDestY );
736  try
737  {
738  if ( ct )
739  {
740  mCPMatrix[theRow][theCol] = ct->transform( myDestPoint );
741  mCPLegalMatrix[theRow][theCol] = true;
742  }
743  else
744  {
745  mCPLegalMatrix[theRow][theCol] = false;
746  }
747  }
748  catch ( QgsCsException &e )
749  {
750  Q_UNUSED( e );
751  // Caught an error in transform
752  mCPLegalMatrix[theRow][theCol] = false;
753  }
754 }
755 
756 bool QgsRasterProjector::calcRow( int theRow, const QgsCoordinateTransform* ct )
757 {
758  QgsDebugMsgLevel( QString( "theRow = %1" ).arg( theRow ), 3 );
759  for ( int i = 0; i < mCPCols; i++ )
760  {
761  calcCP( theRow, i, ct );
762  }
763 
764  return true;
765 }
766 
767 bool QgsRasterProjector::calcCol( int theCol, const QgsCoordinateTransform* ct )
768 {
769  QgsDebugMsgLevel( QString( "theCol = %1" ).arg( theCol ), 3 );
770  for ( int i = 0; i < mCPRows; i++ )
771  {
772  calcCP( i, theCol, ct );
773  }
774 
775  return true;
776 }
777 
778 bool QgsRasterProjector::checkCols( const QgsCoordinateTransform* ct )
779 {
780  if ( !ct )
781  {
782  return false;
783  }
784 
785  for ( int c = 0; c < mCPCols; c++ )
786  {
787  for ( int r = 1; r < mCPRows - 1; r += 2 )
788  {
789  double myDestX, myDestY;
790  destPointOnCPMatrix( r, c, &myDestX, &myDestY );
791  QgsPoint myDestPoint( myDestX, myDestY );
792 
793  QgsPoint mySrcPoint1 = mCPMatrix[r-1][c];
794  QgsPoint mySrcPoint2 = mCPMatrix[r][c];
795  QgsPoint mySrcPoint3 = mCPMatrix[r+1][c];
796 
797  QgsPoint mySrcApprox(( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 );
798  if ( !mCPLegalMatrix[r-1][c] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r+1][c] )
799  {
800  // There was an error earlier in transform, just abort
801  return false;
802  }
803  try
804  {
805  QgsPoint myDestApprox = ct->transform( mySrcApprox, QgsCoordinateTransform::ReverseTransform );
806  double mySqrDist = myDestApprox.sqrDist( myDestPoint );
807  if ( mySqrDist > mSqrTolerance )
808  {
809  return false;
810  }
811  }
812  catch ( QgsCsException &e )
813  {
814  Q_UNUSED( e );
815  // Caught an error in transform
816  return false;
817  }
818  }
819  }
820  return true;
821 }
822 
823 bool QgsRasterProjector::checkRows( const QgsCoordinateTransform* ct )
824 {
825  if ( !ct )
826  {
827  return false;
828  }
829 
830  for ( int r = 0; r < mCPRows; r++ )
831  {
832  for ( int c = 1; c < mCPCols - 1; c += 2 )
833  {
834  double myDestX, myDestY;
835  destPointOnCPMatrix( r, c, &myDestX, &myDestY );
836 
837  QgsPoint myDestPoint( myDestX, myDestY );
838  QgsPoint mySrcPoint1 = mCPMatrix[r][c-1];
839  QgsPoint mySrcPoint2 = mCPMatrix[r][c];
840  QgsPoint mySrcPoint3 = mCPMatrix[r][c+1];
841 
842  QgsPoint mySrcApprox(( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 );
843  if ( !mCPLegalMatrix[r][c-1] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r][c+1] )
844  {
845  // There was an error earlier in transform, just abort
846  return false;
847  }
848  try
849  {
850  QgsPoint myDestApprox = ct->transform( mySrcApprox, QgsCoordinateTransform::ReverseTransform );
851  double mySqrDist = myDestApprox.sqrDist( myDestPoint );
852  if ( mySqrDist > mSqrTolerance )
853  {
854  return false;
855  }
856  }
857  catch ( QgsCsException &e )
858  {
859  Q_UNUSED( e );
860  // Caught an error in transform
861  return false;
862  }
863  }
864  }
865  return true;
866 }
867 
869 {
870  switch ( precision )
871  {
872  case Approximate:
873  return tr( "Approximate" );
874  case Exact:
875  return tr( "Exact" );
876  }
877  return "Unknown";
878 }
879 
880 QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & extent, int width, int height )
881 {
882  QgsDebugMsg( QString( "extent:\n%1" ).arg( extent.toString() ) );
883  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) );
884  if ( !mInput )
885  {
886  QgsDebugMsg( "Input not set" );
887  return new QgsRasterBlock();
888  }
889 
890  if ( ! mSrcCRS.isValid() || ! mDestCRS.isValid() || mSrcCRS == mDestCRS )
891  {
892  QgsDebugMsg( "No projection necessary" );
893  return mInput->block( bandNo, extent, width, height );
894  }
895 
896  mDestExtent = extent;
897  mDestRows = height;
898  mDestCols = width;
899  calc();
900 
901  QgsDebugMsg( QString( "srcExtent:\n%1" ).arg( srcExtent().toString() ) );
902  QgsDebugMsg( QString( "srcCols = %1 srcRows = %2" ).arg( srcCols() ).arg( srcRows() ) );
903 
904  // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers
905  if ( srcRows() <= 0 || srcCols() <= 0 )
906  {
907  QgsDebugMsg( "Zero srcRows or srcCols" );
908  return new QgsRasterBlock();
909  }
910 
911  QgsRasterBlock *inputBlock = mInput->block( bandNo, srcExtent(), srcCols(), srcRows() );
912  if ( !inputBlock || inputBlock->isEmpty() )
913  {
914  QgsDebugMsg( "No raster data!" );
915  delete inputBlock;
916  return new QgsRasterBlock();
917  }
918 
919  qgssize pixelSize = QgsRasterBlock::typeSize( mInput->dataType( bandNo ) );
920 
921  QgsRasterBlock *outputBlock;
922  if ( inputBlock->hasNoDataValue() )
923  {
924  outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, inputBlock->noDataValue() );
925  }
926  else
927  {
928  outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
929  }
930  if ( !outputBlock->isValid() )
931  {
932  QgsDebugMsg( "Cannot create block" );
933  delete inputBlock;
934  return outputBlock;
935  }
936 
937  // set output to no data, it should be fast
938  outputBlock->setIsNoData();
939 
940  // No data: because isNoData()/setIsNoData() is slow with respect to simple memcpy,
941  // we use if only if necessary:
942  // 1) no data value exists (numerical) -> memcpy, not necessary isNoData()/setIsNoData()
943  // 2) no data value does not exist but it may contain no data (numerical no data bitmap)
944  // -> must use isNoData()/setIsNoData()
945  // 3) no data are not used (no no data value, no no data bitmap) -> simple memcpy
946  // 4) image - simple memcpy
947 
948  // To copy no data values stored in bitmaps we have to use isNoData()/setIsNoData(),
949  // we cannot fill output block with no data because we use memcpy for data, not setValue().
950  bool doNoData = !QgsRasterBlock::typeIsNumeric( inputBlock->dataType() ) && inputBlock->hasNoData() && !inputBlock->hasNoDataValue();
951 
952  const QgsCoordinateTransform* inverseCt = 0;
953  if ( !mApproximate )
954  {
955  inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform );
956  }
957 
958  outputBlock->setIsNoData();
959 
960  int srcRow, srcCol;
961  for ( int i = 0; i < height; ++i )
962  {
963  for ( int j = 0; j < width; ++j )
964  {
965  bool inside = srcRowCol( i, j, &srcRow, &srcCol, inverseCt );
966  if ( !inside ) continue; // we have everything set to no data
967 
968  qgssize srcIndex = ( qgssize )srcRow * mSrcCols + srcCol;
969  QgsDebugMsgLevel( QString( "row = %1 col = %2 srcRow = %3 srcCol = %4" ).arg( i ).arg( j ).arg( srcRow ).arg( srcCol ), 5 );
970 
971  // isNoData() may be slow so we check doNoData first
972  if ( doNoData && inputBlock->isNoData( srcRow, srcCol ) )
973  {
974  outputBlock->setIsNoData( i, j );
975  continue;
976  }
977 
978  qgssize destIndex = ( qgssize )i * width + j;
979  char *srcBits = inputBlock->bits( srcIndex );
980  char *destBits = outputBlock->bits( destIndex );
981  if ( !srcBits )
982  {
983  QgsDebugMsg( QString( "Cannot get input block data: row = %1 col = %2" ).arg( i ).arg( j ) );
984  continue;
985  }
986  if ( !destBits )
987  {
988  QgsDebugMsg( QString( "Cannot set output block data: srcRow = %1 srcCol = %2" ).arg( srcRow ).arg( srcCol ) );
989  continue;
990  }
991  memcpy( destBits, srcBits, pixelSize );
992  outputBlock->setIsData( i, j );
993  }
994  }
995 
996  delete inputBlock;
997 
998  return outputBlock;
999 }
1000 
1001 bool QgsRasterProjector::destExtentSize( const QgsRectangle& theSrcExtent, int theSrcXSize, int theSrcYSize,
1002  QgsRectangle& theDestExtent, int& theDestXSize, int& theDestYSize )
1003 {
1004  if ( theSrcExtent.isEmpty() || theSrcXSize <= 0 || theSrcYSize <= 0 )
1005  {
1006  return false;
1007  }
1008  const QgsCoordinateTransform* ct = QgsCoordinateTransformCache::instance()->transform( mSrcCRS.authid(), mDestCRS.authid(), mSrcDatumTransform, mDestDatumTransform );
1009 
1010  return extentSize( ct, theSrcExtent, theSrcXSize, theSrcYSize, theDestExtent, theDestXSize, theDestYSize );
1011 }
1012 
1014  const QgsRectangle& theSrcExtent, int theSrcXSize, int theSrcYSize,
1015  QgsRectangle& theDestExtent, int& theDestXSize, int& theDestYSize )
1016 {
1017  if ( theSrcExtent.isEmpty() || theSrcXSize <= 0 || theSrcYSize <= 0 )
1018  {
1019  return false;
1020  }
1021 
1022  theDestExtent = ct->transformBoundingBox( theSrcExtent );
1023 
1024  // We reproject pixel rectangle from 9 points matrix of source extent, of course, it gives
1025  // bigger xRes,yRes than reprojected edges (envelope)
1026  double srcXStep = theSrcExtent.width() / 3;
1027  double srcYStep = theSrcExtent.height() / 3;
1028  double srcXRes = theSrcExtent.width() / theSrcXSize;
1029  double srcYRes = theSrcExtent.height() / theSrcYSize;
1030  double destXRes = std::numeric_limits<double>::max();
1031  double destYRes = std::numeric_limits<double>::max();
1032 
1033  for ( int i = 0; i < 3; i++ )
1034  {
1035  double x = theSrcExtent.xMinimum() + i * srcXStep;
1036  for ( int j = 0; j < 3; j++ )
1037  {
1038  double y = theSrcExtent.yMinimum() + j * srcYStep;
1039  QgsRectangle srcRectangle( x - srcXRes / 2, y - srcYRes / 2, x + srcXRes / 2, y + srcYRes / 2 );
1040  QgsRectangle destRectangle = ct->transformBoundingBox( srcRectangle );
1041  if ( destRectangle.width() > 0 )
1042  {
1043  destXRes = std::min( destXRes, destRectangle.width() );
1044  }
1045  if ( destRectangle.height() > 0 )
1046  {
1047  destYRes = std::min( destYRes, destRectangle.height() );
1048  }
1049  }
1050  }
1051  theDestXSize = std::max( 1, ( int )( theDestExtent.width() / destYRes ) );
1052  theDestYSize = std::max( 1, ( int )( theDestExtent.height() / destYRes ) );
1053 
1054  return true;
1055 }
1056 
virtual int bandCount() const =0
Get number of bands.
void clear()
A rectangle specified with double values.
Definition: qgsrectangle.h:35
bool isEmpty() const
test if rectangle is empty.
void setCRS(const QgsCoordinateReferenceSystem &theSrcCRS, const QgsCoordinateReferenceSystem &theDestCRS, int srcDatumTransform=-1, int destDatumTransform=-1)
set source and destination CRS
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:167
Approximate (default), fast but possibly inaccurate.
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:192
static bool typeIsNumeric(QGis::DataType type)
Returns true if data type is numeric.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
bool contains(const QgsRectangle &rect) const
return true when rectangle contains other rectangle
virtual const QgsRasterInterface * srcInput() const
Get source / raw input, the first in pipe, usually provider.
double noDataValue() const
Return no data value.
QGis::DataType dataType() const
Returns data type.
QgsPoint transform(const QgsPoint &p, TransformDirection direction=ForwardTransform) const
static QgsCoordinateTransformCache * instance()
Definition: qgscrscache.cpp:22
double sqrDist(double x, double y) const
Returns the squared distance between this point and x,y.
Definition: qgspoint.cpp:345
bool isNoData(int row, int column)
Check if value at position is no data.
double x() const
Definition: qgspoint.h:126
virtual int ySize() const
bool setIsNoData(int row, int column)
Set no data on pixel.
const QgsCoordinateTransform * transform(const QString &srcAuthId, const QString &destAuthId, int srcDatumTransform=-1, int destDatumTransform=-1)
Returns coordinate transformation.
Definition: qgscrscache.cpp:37
void transformInPlace(double &x, double &y, double &z, TransformDirection direction=ForwardTransform) const
void combineExtentWith(QgsRectangle *rect)
expand the rectangle so that covers both the original rectangle and the given rectangle ...
void append(const T &value)
~QgsRasterProjector()
The destructor.
bool hasNoData() const
Returns true if the block may contain no data.
Raster data container.
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:197
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height) override
Read block of data using given extent and size.
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:182
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:172
QString toString() const
String representation of the point (x,y)
Definition: qgspoint.cpp:126
static int typeSize(int dataType)
virtual QGis::DataType dataType(int bandNo) const =0
Returns data type for the band specified by number.
QgsRasterInterface * clone() const override
Clone itself, create deep copy.
Base class for processing filters like renderers, reprojector, resampler etc.
A class to represent a point.
Definition: qgspoint.h:63
unsigned long long qgssize
qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition: qgis.h:449
int min(int a, int b)
Definition: util.h:93
void setX(double x)
Definition: qgspoint.h:103
virtual int capabilities() const
Returns a bitmask containing the supported capabilities.
void setY(double y)
Definition: qgspoint.h:111
virtual QgsRectangle extent() override=0
Get the extent of the data source.
virtual QgsRectangle extent()
Get the extent of the interface.
bool hasNoDataValue() const
True if the block has no data value.
char * bits(int row, int column)
Get pointer to data.
Precision
Precison defines if each pixel is reprojected or approximate reprojection based on an approximation m...
virtual int xSize() const
Get raster size.
void insert(int i, const T &value)
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:177
QgsRectangle intersect(const QgsRectangle *rect) const
return the intersection with the given rectangle
Class for storing a coordinate reference system (CRS)
DataType
Raster data types.
Definition: qgis.h:204
virtual QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height)=0
Read block of data using given extent and size.
Class for doing transforms between two map coordinate systems.
static bool extentSize(const QgsCoordinateTransform *ct, const QgsRectangle &theSrcExtent, int theSrcXSize, int theSrcYSize, QgsRectangle &theDestExtent, int &theDestXSize, int &theDestYSize)
Calculate destination extent and size from source extent and size.
double y() const
Definition: qgspoint.h:134
Exact, precise but slow.
Custom exception class for Coordinate Reference System related exceptions.
QGis::DataType dataType(int bandNo) const override
Returns data type for the band specified by number.
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:202
bool destExtentSize(const QgsRectangle &theSrcExtent, int theSrcXSize, int theSrcYSize, QgsRectangle &theDestExtent, int &theDestXSize, int &theDestYSize)
Calculate destination extent and size from source extent and size.
QgsRasterInterface * mInput
QgsRasterProjector & operator=(const QgsRasterProjector &projector)
QString toString(bool automaticPrecision=false) const
returns string representation of form xmin,ymin xmax,ymax
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:187
int max(int a, int b)
Definition: util.h:87
static QString precisionLabel(Precision precision)
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:162
QgsRectangle transformBoundingBox(const QgsRectangle &theRect, TransformDirection direction=ForwardTransform, const bool handle180Crossover=false) const
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:207
bool isEmpty() const
Returns true if block is empty, i.e.
int bandCount() const override
Get number of bands.
Base class for raster data providers.
#define tr(sourceText)