QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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 "qgsrasterprojector.h"
18
19#include <algorithm>
20
22#include "qgsexception.h"
23#include "qgslogger.h"
25
26#include "moc_qgsrasterprojector.cpp"
27
28Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
30 : QgsRasterInterface( nullptr )
31{
32 QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
33}
35
36
38{
39 QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
41 projector->mSrcCRS = mSrcCRS;
42 projector->mDestCRS = mDestCRS;
43 projector->mTransformContext = mTransformContext;
44
46 projector->mSrcDatumTransform = mSrcDatumTransform;
47 projector->mDestDatumTransform = mDestDatumTransform;
49
50 projector->mPrecision = mPrecision;
51 return projector;
52}
53
55{
56 if ( mInput ) return mInput->bandCount();
57
58 return 0;
59}
60
62{
63 if ( mInput ) return mInput->dataType( bandNo );
64
66}
67
68
70
72 const QgsCoordinateReferenceSystem &destCRS,
73 int srcDatumTransform,
74 int destDatumTransform )
75{
76 mSrcCRS = srcCRS;
77 mDestCRS = destCRS;
79 mSrcDatumTransform = srcDatumTransform;
80 mDestDatumTransform = destDatumTransform;
82}
83
85{
86 mSrcCRS = srcCRS;
87 mDestCRS = destCRS;
88 mTransformContext = transformContext;
90 mSrcDatumTransform = -1;
91 mDestDatumTransform = -1;
93}
94
95
96ProjectorData::ProjectorData( const QgsRectangle &extent, int width, int height, QgsRasterInterface *input, const QgsCoordinateTransform &inverseCt, QgsRasterProjector::Precision precision, QgsRasterBlockFeedback *feedback )
97 : mInverseCt( inverseCt )
98 , mDestExtent( extent )
99 , mDestRows( height )
100 , mDestCols( width )
101{
102 QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
103
104 // Get max source resolution and extent if possible
105 if ( input )
106 {
107 QgsRasterDataProvider *provider = dynamic_cast<QgsRasterDataProvider *>( input->sourceInput() );
108 if ( provider )
109 {
110 // If provider-side resampling is possible, we will get a much better looking
111 // result by not requesting at the maximum resolution and then doing nearest
112 // resampling here. A real fix would be to do resampling during reprojection
113 // however.
116 {
117 mMaxSrcXRes = provider->extent().width() / provider->xSize();
118 mMaxSrcYRes = provider->extent().height() / provider->ySize();
119 }
120 // Get source extent
121 if ( mExtent.isEmpty() )
122 {
123 mExtent = provider->extent();
124 }
125 }
126 }
127
128 mDestXRes = mDestExtent.width() / ( mDestCols );
129 mDestYRes = mDestExtent.height() / ( mDestRows );
130
131 // Calculate tolerance
132 // TODO: Think it over better
133 // Note: we are checking on matrix each even point, that means that the real error
134 // in that moment is approximately half size
135 const double myDestRes = mDestXRes < mDestYRes ? mDestXRes : mDestYRes;
136 mSqrTolerance = myDestRes * myDestRes;
137
139 {
140 mApproximate = true;
141 }
142 else
143 {
144 mApproximate = false;
145 }
146
147 // Always try to calculate mCPMatrix, it is used in calcSrcExtent() for both Approximate and Exact
148 // Initialize the matrix by corners and middle points
149 mCPCols = mCPRows = 3;
150
151 // For WebMercator to geographic, if the bounding box is symmetric in Y,
152 // and we only use 3 sample points, we would believe that there is perfect
153 // linear approximation, resulting in wrong reprojection
154 // (see https://github.com/qgis/QGIS/issues/34518).
155 // But if we use 5 sample points, we will detect the non-linearity and will
156 // refine the CPMatrix appropriately.
157 if ( std::fabs( -mDestExtent.yMinimum() - mDestExtent.yMaximum() ) / height < 0.5 * mDestYRes )
158 mCPRows = 5;
159
160 for ( int i = 0; i < mCPRows; i++ )
161 {
162 QList<QgsPointXY> myRow;
163 myRow.append( QgsPointXY() );
164 myRow.append( QgsPointXY() );
165 myRow.append( QgsPointXY() );
166 mCPMatrix.insert( i, myRow );
167 // And the legal points
168 QList<bool> myLegalRow;
169 myLegalRow.append( bool( false ) );
170 myLegalRow.append( bool( false ) );
171 myLegalRow.append( bool( false ) );
172 mCPLegalMatrix.insert( i, myLegalRow );
173 }
174 for ( int i = 0; i < mCPRows; i++ )
175 {
176 calcRow( i, inverseCt );
177 }
178
179 while ( true )
180 {
181 const bool myColsOK = checkCols( inverseCt );
182 if ( !myColsOK )
183 {
184 insertRows( inverseCt );
185 }
186 const bool myRowsOK = checkRows( inverseCt );
187 if ( !myRowsOK )
188 {
189 insertCols( inverseCt );
190 }
191 if ( myColsOK && myRowsOK )
192 {
193 QgsDebugMsgLevel( QStringLiteral( "CP matrix within tolerance" ), 4 );
194 break;
195 }
196 // What is the maximum reasonable size of transformatio matrix?
197 // TODO: consider better when to break - ratio
198 if ( mCPRows * mCPCols > 0.25 * mDestRows * mDestCols )
199 //if ( mCPRows * mCPCols > mDestRows * mDestCols )
200 {
201 QgsDebugMsgLevel( QStringLiteral( "Too large CP matrix" ), 4 );
202 mApproximate = false;
203 break;
204 }
205 if ( feedback && feedback->isCanceled() )
206 {
207 return;
208 }
209 }
210 QgsDebugMsgLevel( QStringLiteral( "CPMatrix size: mCPRows = %1 mCPCols = %2" ).arg( mCPRows ).arg( mCPCols ), 4 );
211 mDestRowsPerMatrixRow = static_cast< double >( mDestRows ) / ( mCPRows - 1 );
212 mDestColsPerMatrixCol = static_cast< double >( mDestCols ) / ( mCPCols - 1 );
213
214#if 0
215 QgsDebugMsgLevel( QStringLiteral( "CPMatrix:" ), 5 );
216 QgsDebugMsgLevel( cpToString(), 5 );
217#endif
218
219 // init helper points
220 pHelperTop = new QgsPointXY[mDestCols];
221 pHelperBottom = new QgsPointXY[mDestCols];
222 calcHelper( 0, pHelperTop );
223 calcHelper( 1, pHelperBottom );
224 mHelperTopRow = 0;
225
226 // Calculate source dimensions
227 calcSrcExtent();
228 calcSrcRowsCols();
229 mSrcYRes = mSrcExtent.height() / mSrcRows;
230 mSrcXRes = mSrcExtent.width() / mSrcCols;
231}
232
233ProjectorData::~ProjectorData()
234{
235 delete[] pHelperTop;
236 delete[] pHelperBottom;
237}
238
239
240void ProjectorData::calcSrcExtent()
241{
242 /* Run around the mCPMatrix and find source extent */
243 // Attention, source limits are not necessarily on destination edges, e.g.
244 // for destination EPSG:32661 Polar Stereographic and source EPSG:4326,
245 // the maximum y may be in the middle of destination extent
246 // TODO: How to find extent exactly and quickly?
247 // For now, we run through all matrix
248 // mCPMatrix is used for both Approximate and Exact because QgsCoordinateTransform::transformBoundingBox()
249 // is not precise enough, see #13665
250 QgsPointXY myPoint = mCPMatrix[0][0];
251 mSrcExtent = QgsRectangle( myPoint.x(), myPoint.y(), myPoint.x(), myPoint.y() );
252 for ( int i = 0; i < mCPRows; i++ )
253 {
254 for ( int j = 0; j < mCPCols ; j++ )
255 {
256 myPoint = mCPMatrix[i][j];
257 if ( mCPLegalMatrix[i][j] )
258 {
259 mSrcExtent.combineExtentWith( myPoint.x(), myPoint.y() );
260 }
261 }
262 }
263 // Expand a bit to avoid possible approx coords falling out because of representation error?
264
265 // Combine with maximum source extent
266 mSrcExtent = mSrcExtent.intersect( mExtent );
267
268 // If mMaxSrcXRes, mMaxSrcYRes are defined (fixed src resolution)
269 // align extent to src resolution to avoid jumping of reprojected pixels
270 // when shifting resampled grid.
271 // Important especially if we are over mMaxSrcXRes, mMaxSrcYRes limits
272 // Note however, that preceding filters (like resampler) may read data
273 // on different resolution.
274
275 QgsDebugMsgLevel( "mSrcExtent = " + mSrcExtent.toString(), 4 );
276 QgsDebugMsgLevel( "mExtent = " + mExtent.toString(), 4 );
277 if ( !mExtent.isEmpty() )
278 {
279 if ( mMaxSrcXRes > 0 )
280 {
281 // with floor/ceil it should work correctly also for mSrcExtent.xMinimum() < mExtent.xMinimum()
282 double col = std::floor( ( mSrcExtent.xMinimum() - mExtent.xMinimum() ) / mMaxSrcXRes );
283 double x = mExtent.xMinimum() + col * mMaxSrcXRes;
284 mSrcExtent.setXMinimum( x );
285
286 col = std::ceil( ( mSrcExtent.xMaximum() - mExtent.xMinimum() ) / mMaxSrcXRes );
287 x = mExtent.xMinimum() + col * mMaxSrcXRes;
288 mSrcExtent.setXMaximum( x );
289 }
290 if ( mMaxSrcYRes > 0 )
291 {
292 double row = std::floor( ( mExtent.yMaximum() - mSrcExtent.yMaximum() ) / mMaxSrcYRes );
293 double y = mExtent.yMaximum() - row * mMaxSrcYRes;
294 mSrcExtent.setYMaximum( y );
295
296 row = std::ceil( ( mExtent.yMaximum() - mSrcExtent.yMinimum() ) / mMaxSrcYRes );
297 y = mExtent.yMaximum() - row * mMaxSrcYRes;
298 mSrcExtent.setYMinimum( y );
299 }
300 }
301 QgsDebugMsgLevel( "mSrcExtent = " + mSrcExtent.toString(), 4 );
302}
303
304QString ProjectorData::cpToString() const
305{
306 QString myString;
307 for ( int i = 0; i < mCPRows; i++ )
308 {
309 if ( i > 0 )
310 myString += '\n';
311 for ( int j = 0; j < mCPCols; j++ )
312 {
313 if ( j > 0 )
314 myString += QLatin1String( " " );
315 const QgsPointXY myPoint = mCPMatrix[i][j];
316 if ( mCPLegalMatrix[i][j] )
317 {
318 myString += myPoint.toString();
319 }
320 else
321 {
322 myString += QLatin1String( "(-,-)" );
323 }
324 }
325 }
326 return myString;
327}
328
329void ProjectorData::calcSrcRowsCols()
330{
331 // Wee need to calculate minimum cell size in the source
332 // TODO: Think it over better, what is the right source resolution?
333 // Taking distances between cell centers projected to source along source
334 // axis would result in very high resolution
335 // TODO: different resolution for rows and cols ?
336
337 double myMinSize = std::numeric_limits<double>::max();
338
339 if ( mApproximate )
340 {
341 double myMaxSize = 0;
342
343 // For now, we take cell sizes projected to source but not to source axes
344 const double myDestColsPerMatrixCell = static_cast< double >( mDestCols ) / mCPCols;
345 const double myDestRowsPerMatrixCell = static_cast< double >( mDestRows ) / mCPRows;
346 QgsDebugMsgLevel( QStringLiteral( "myDestColsPerMatrixCell = %1 myDestRowsPerMatrixCell = %2" ).arg( myDestColsPerMatrixCell ).arg( myDestRowsPerMatrixCell ), 4 );
347 for ( int i = 0; i < mCPRows - 1; i++ )
348 {
349 for ( int j = 0; j < mCPCols - 1; j++ )
350 {
351 const QgsPointXY myPointA = mCPMatrix[i][j];
352 const QgsPointXY myPointB = mCPMatrix[i][j + 1];
353 const QgsPointXY myPointC = mCPMatrix[i + 1][j];
354 if ( mCPLegalMatrix[i][j] && mCPLegalMatrix[i][j + 1] && mCPLegalMatrix[i + 1][j] )
355 {
356 double mySize = std::sqrt( myPointA.sqrDist( myPointB ) ) / myDestColsPerMatrixCell;
357 if ( mySize < myMinSize )
358 myMinSize = mySize;
359 if ( mySize > myMaxSize )
360 myMaxSize = mySize;
361
362 mySize = std::sqrt( myPointA.sqrDist( myPointC ) ) / myDestRowsPerMatrixCell;
363 if ( mySize < myMinSize )
364 myMinSize = mySize;
365 if ( mySize > myMaxSize )
366 myMaxSize = mySize;
367 }
368 }
369 }
370 // Limit resolution to 1/10th of the maximum resolution to avoid issues
371 // with for example WebMercator at high northings that result in very small
372 // latitude differences.
373 if ( myMinSize < 0.1 * myMaxSize )
374 myMinSize = 0.1 * myMaxSize;
375 }
376 else
377 {
378 // take highest from corners, points in in the middle of corners and center (3 x 3 )
379 //double
380 QgsRectangle srcExtent;
381 int srcXSize, srcYSize;
382 if ( QgsRasterProjector::extentSize( mInverseCt, mDestExtent, mDestCols, mDestRows, srcExtent, srcXSize, srcYSize ) )
383 {
384 const double srcXRes = srcExtent.width() / srcXSize;
385 const double srcYRes = srcExtent.height() / srcYSize;
386 myMinSize = std::min( srcXRes, srcYRes );
387 }
388 else
389 {
390 QgsDebugError( QStringLiteral( "Cannot get src extent/size" ) );
391 }
392 }
393
394 // Make it a bit higher resolution
395 // TODO: find the best coefficient, attention, increasing resolution for WMS
396 // is changing WMS content
397 myMinSize *= 0.75;
398
399 QgsDebugMsgLevel( QStringLiteral( "mMaxSrcXRes = %1 mMaxSrcYRes = %2" ).arg( mMaxSrcXRes ).arg( mMaxSrcYRes ), 4 );
400 // mMaxSrcXRes, mMaxSrcYRes may be 0 - no limit (WMS)
401 const double myMinXSize = mMaxSrcXRes > myMinSize ? mMaxSrcXRes : myMinSize;
402 const double myMinYSize = mMaxSrcYRes > myMinSize ? mMaxSrcYRes : myMinSize;
403 QgsDebugMsgLevel( QStringLiteral( "myMinXSize = %1 myMinYSize = %2" ).arg( myMinXSize ).arg( myMinYSize ), 4 );
404 QgsDebugMsgLevel( QStringLiteral( "mSrcExtent.width = %1 mSrcExtent.height = %2" ).arg( mSrcExtent.width() ).arg( mSrcExtent.height() ), 4 );
405
406 // we have to round to keep alignment set in calcSrcExtent
407 // Limit to 10x the source dimensions to avoid excessive memory allocation
408 // and processing time.
409 double dblSrcRows = mSrcExtent.height() / myMinYSize;
410 if ( dblSrcRows > mDestRows * 10 )
411 mSrcRows = mDestRows * 10;
412 else
413 mSrcRows = static_cast< int >( std::round( dblSrcRows ) );
414
415 double dblSrcCols = mSrcExtent.width() / myMinXSize;
416 if ( dblSrcCols > mDestCols * 10 )
417 mSrcCols = mDestCols * 10;
418 else
419 mSrcCols = static_cast< int >( std::round( dblSrcCols ) );
420
421 QgsDebugMsgLevel( QStringLiteral( "mSrcRows = %1 mSrcCols = %2" ).arg( mSrcRows ).arg( mSrcCols ), 4 );
422}
423
424
425inline void ProjectorData::destPointOnCPMatrix( int row, int col, double *theX, double *theY ) const
426{
427 *theX = mDestExtent.xMinimum() + col * mDestExtent.width() / ( mCPCols - 1 );
428 *theY = mDestExtent.yMaximum() - row * mDestExtent.height() / ( mCPRows - 1 );
429}
430
431inline int ProjectorData::matrixRow( int destRow ) const
432{
433 return static_cast< int >( std::floor( ( destRow + 0.5 ) / mDestRowsPerMatrixRow ) );
434}
435inline int ProjectorData::matrixCol( int destCol ) const
436{
437 return static_cast< int >( std::floor( ( destCol + 0.5 ) / mDestColsPerMatrixCol ) );
438}
439
440void ProjectorData::calcHelper( int matrixRow, QgsPointXY *points )
441{
442 // TODO?: should we also precalc dest cell center coordinates for x and y?
443 for ( int myDestCol = 0; myDestCol < mDestCols; myDestCol++ )
444 {
445 const double myDestX = mDestExtent.xMinimum() + ( myDestCol + 0.5 ) * mDestXRes;
446
447 const int myMatrixCol = matrixCol( myDestCol );
448
449 double myDestXMin, myDestYMin, myDestXMax, myDestYMax;
450
451 destPointOnCPMatrix( matrixRow, myMatrixCol, &myDestXMin, &myDestYMin );
452 destPointOnCPMatrix( matrixRow, myMatrixCol + 1, &myDestXMax, &myDestYMax );
453
454 const double xfrac = ( myDestX - myDestXMin ) / ( myDestXMax - myDestXMin );
455
456 const QgsPointXY &mySrcPoint0 = mCPMatrix[matrixRow][myMatrixCol];
457 const QgsPointXY &mySrcPoint1 = mCPMatrix[matrixRow][myMatrixCol + 1];
458 const double s = mySrcPoint0.x() + ( mySrcPoint1.x() - mySrcPoint0.x() ) * xfrac;
459 const double t = mySrcPoint0.y() + ( mySrcPoint1.y() - mySrcPoint0.y() ) * xfrac;
460
461 points[myDestCol].setX( s );
462 points[myDestCol].setY( t );
463 }
464}
465
466void ProjectorData::nextHelper()
467{
468 // We just switch pHelperTop and pHelperBottom, memory is not lost
469 QgsPointXY *tmp = nullptr;
470 tmp = pHelperTop;
471 pHelperTop = pHelperBottom;
472 pHelperBottom = tmp;
473 calcHelper( mHelperTopRow + 2, pHelperBottom );
474 mHelperTopRow++;
475}
476
477bool ProjectorData::srcRowCol( int destRow, int destCol, int *srcRow, int *srcCol )
478{
479 if ( mApproximate )
480 {
481 return approximateSrcRowCol( destRow, destCol, srcRow, srcCol );
482 }
483 else
484 {
485 return preciseSrcRowCol( destRow, destCol, srcRow, srcCol );
486 }
487}
488
489bool ProjectorData::preciseSrcRowCol( int destRow, int destCol, int *srcRow, int *srcCol )
490{
491#if 0 // too slow, even if we only run it on debug builds!
492 QgsDebugMsgLevel( QStringLiteral( "theDestRow = %1" ).arg( destRow ), 5 );
493 QgsDebugMsgLevel( QStringLiteral( "theDestRow = %1 mDestExtent.yMaximum() = %2 mDestYRes = %3" ).arg( destRow ).arg( mDestExtent.yMaximum() ).arg( mDestYRes ), 5 );
494#endif
495
496 // Get coordinate of center of destination cell
497 double x = mDestExtent.xMinimum() + ( destCol + 0.5 ) * mDestXRes;
498 double y = mDestExtent.yMaximum() - ( destRow + 0.5 ) * mDestYRes;
499 double z = 0;
500
501#if 0
502 QgsDebugMsgLevel( QStringLiteral( "x = %1 y = %2" ).arg( x ).arg( y ), 5 );
503#endif
504
505 if ( mInverseCt.isValid() )
506 {
507 try
508 {
509 mInverseCt.transformInPlace( x, y, z );
510 }
511 catch ( QgsCsException & )
512 {
513 return false;
514 }
515 }
516
517#if 0
518 QgsDebugMsgLevel( QStringLiteral( "x = %1 y = %2" ).arg( x ).arg( y ), 5 );
519#endif
520
521 if ( !mExtent.contains( x, y ) )
522 {
523 return false;
524 }
525 // Get source row col
526 *srcRow = static_cast< int >( std::floor( ( mSrcExtent.yMaximum() - y ) / mSrcYRes ) );
527 *srcCol = static_cast< int >( std::floor( ( x - mSrcExtent.xMinimum() ) / mSrcXRes ) );
528#if 0
529 QgsDebugMsgLevel( QStringLiteral( "mSrcExtent.yMinimum() = %1 mSrcExtent.yMaximum() = %2 mSrcYRes = %3" ).arg( mSrcExtent.yMinimum() ).arg( mSrcExtent.yMaximum() ).arg( mSrcYRes ), 5 );
530 QgsDebugMsgLevel( QStringLiteral( "theSrcRow = %1 srcCol = %2" ).arg( *srcRow ).arg( *srcCol ), 5 );
531#endif
532
533 // With epsg 32661 (Polar Stereographic) it was happening that *srcCol == mSrcCols
534 // For now silently correct limits to avoid crashes
535 // TODO: review
536 // should not happen
537 if ( *srcRow >= mSrcRows ) return false;
538 if ( *srcRow < 0 ) return false;
539 if ( *srcCol >= mSrcCols ) return false;
540 if ( *srcCol < 0 ) return false;
541
542 return true;
543}
544
545bool ProjectorData::approximateSrcRowCol( int destRow, int destCol, int *srcRow, int *srcCol )
546{
547 const int myMatrixRow = matrixRow( destRow );
548 const int myMatrixCol = matrixCol( destCol );
549
550 if ( myMatrixRow > mHelperTopRow )
551 {
552 // TODO: make it more robust (for random, not sequential reading)
553 nextHelper();
554 }
555
556 const double myDestY = mDestExtent.yMaximum() - ( destRow + 0.5 ) * mDestYRes;
557
558 // See the schema in javax.media.jai.WarpGrid doc (but up side down)
559 // TODO: use some kind of cache of values which can be reused
560 double myDestXMin, myDestYMin, myDestXMax, myDestYMax;
561
562 destPointOnCPMatrix( myMatrixRow + 1, myMatrixCol, &myDestXMin, &myDestYMin );
563 destPointOnCPMatrix( myMatrixRow, myMatrixCol + 1, &myDestXMax, &myDestYMax );
564
565 const double yfrac = ( myDestY - myDestYMin ) / ( myDestYMax - myDestYMin );
566
567 const QgsPointXY &myTop = pHelperTop[destCol];
568 const QgsPointXY &myBot = pHelperBottom[destCol];
569
570 // Warning: this is very SLOW compared to the following code!:
571 //double mySrcX = myBot.x() + (myTop.x() - myBot.x()) * yfrac;
572 //double mySrcY = myBot.y() + (myTop.y() - myBot.y()) * yfrac;
573
574 const double tx = myTop.x();
575 const double ty = myTop.y();
576 const double bx = myBot.x();
577 const double by = myBot.y();
578 const double mySrcX = bx + ( tx - bx ) * yfrac;
579 const double mySrcY = by + ( ty - by ) * yfrac;
580
581 if ( !mExtent.contains( mySrcX, mySrcY ) )
582 {
583 return false;
584 }
585
586 // TODO: check again cell selection (coor is in the middle)
587
588 *srcRow = static_cast< int >( std::floor( ( mSrcExtent.yMaximum() - mySrcY ) / mSrcYRes ) );
589 *srcCol = static_cast< int >( std::floor( ( mySrcX - mSrcExtent.xMinimum() ) / mSrcXRes ) );
590
591 // For now silently correct limits to avoid crashes
592 // TODO: review
593 // should not happen
594 if ( *srcRow >= mSrcRows ) return false;
595 if ( *srcRow < 0 ) return false;
596 if ( *srcCol >= mSrcCols ) return false;
597 if ( *srcCol < 0 ) return false;
598
599 return true;
600}
601
602void ProjectorData::insertRows( const QgsCoordinateTransform &ct )
603{
604 for ( int r = 0; r < mCPRows - 1; r++ )
605 {
606 QList<QgsPointXY> myRow;
607 QList<bool> myLegalRow;
608 myRow.reserve( mCPCols );
609 myLegalRow.reserve( mCPCols );
610 for ( int c = 0; c < mCPCols; ++c )
611 {
612 myRow.append( QgsPointXY() );
613 myLegalRow.append( false );
614 }
615 QgsDebugMsgLevel( QStringLiteral( "insert new row at %1" ).arg( 1 + r * 2 ), 3 );
616 mCPMatrix.insert( 1 + r * 2, myRow );
617 mCPLegalMatrix.insert( 1 + r * 2, myLegalRow );
618 }
619 mCPRows += mCPRows - 1;
620 for ( int r = 1; r < mCPRows - 1; r += 2 )
621 {
622 calcRow( r, ct );
623 }
624}
625
626void ProjectorData::insertCols( const QgsCoordinateTransform &ct )
627{
628 for ( int r = 0; r < mCPRows; r++ )
629 {
630 for ( int c = 0; c < mCPCols - 1; c++ )
631 {
632 mCPMatrix[r].insert( 1 + c * 2, QgsPointXY() );
633 mCPLegalMatrix[r].insert( 1 + c * 2, false );
634 }
635 }
636 mCPCols += mCPCols - 1;
637 for ( int c = 1; c < mCPCols - 1; c += 2 )
638 {
639 calcCol( c, ct );
640 }
641
642}
643
644void ProjectorData::calcCP( int row, int col, const QgsCoordinateTransform &ct )
645{
646 double myDestX, myDestY;
647 destPointOnCPMatrix( row, col, &myDestX, &myDestY );
648 const QgsPointXY myDestPoint( myDestX, myDestY );
649 try
650 {
651 if ( ct.isValid() )
652 {
653 mCPMatrix[row][col] = ct.transform( myDestPoint );
654 mCPLegalMatrix[row][col] = true;
655 }
656 else
657 {
658 mCPLegalMatrix[row][col] = false;
659 }
660 }
661 catch ( QgsCsException &e )
662 {
663 Q_UNUSED( e )
664 // Caught an error in transform
665 mCPLegalMatrix[row][col] = false;
666 }
667}
668
669bool ProjectorData::calcRow( int row, const QgsCoordinateTransform &ct )
670{
671 QgsDebugMsgLevel( QStringLiteral( "theRow = %1" ).arg( row ), 3 );
672 for ( int i = 0; i < mCPCols; i++ )
673 {
674 calcCP( row, i, ct );
675 }
676
677 return true;
678}
679
680bool ProjectorData::calcCol( int col, const QgsCoordinateTransform &ct )
681{
682 QgsDebugMsgLevel( QStringLiteral( "theCol = %1" ).arg( col ), 3 );
683 for ( int i = 0; i < mCPRows; i++ )
684 {
685 calcCP( i, col, ct );
686 }
687
688 return true;
689}
690
691bool ProjectorData::checkCols( const QgsCoordinateTransform &ct ) const
692{
693 if ( !ct.isValid() )
694 {
695 return false;
696 }
697
698 for ( int c = 0; c < mCPCols; c++ )
699 {
700 for ( int r = 1; r < mCPRows - 1; r += 2 )
701 {
702 double myDestX, myDestY;
703 destPointOnCPMatrix( r, c, &myDestX, &myDestY );
704 const QgsPointXY myDestPoint( myDestX, myDestY );
705
706 const QgsPointXY mySrcPoint1 = mCPMatrix[r - 1][c];
707 const QgsPointXY mySrcPoint2 = mCPMatrix[r][c];
708 const QgsPointXY mySrcPoint3 = mCPMatrix[r + 1][c];
709
710 const QgsPointXY mySrcApprox( ( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 );
711 if ( !mCPLegalMatrix[r - 1][c] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r + 1][c] )
712 {
713 // There was an error earlier in transform, just abort
714 return false;
715 }
716 try
717 {
718 const QgsPointXY myDestApprox = ct.transform( mySrcApprox, Qgis::TransformDirection::Reverse );
719 const double mySqrDist = myDestApprox.sqrDist( myDestPoint );
720 if ( mySqrDist > mSqrTolerance )
721 {
722 return false;
723 }
724 }
725 catch ( QgsCsException &e )
726 {
727 Q_UNUSED( e )
728 // Caught an error in transform
729 return false;
730 }
731 }
732 }
733 return true;
734}
735
736bool ProjectorData::checkRows( const QgsCoordinateTransform &ct ) const
737{
738 if ( !ct.isValid() )
739 {
740 return false;
741 }
742
743 for ( int r = 0; r < mCPRows; r++ )
744 {
745 for ( int c = 1; c < mCPCols - 1; c += 2 )
746 {
747 double myDestX, myDestY;
748 destPointOnCPMatrix( r, c, &myDestX, &myDestY );
749
750 const QgsPointXY myDestPoint( myDestX, myDestY );
751 const QgsPointXY mySrcPoint1 = mCPMatrix[r][c - 1];
752 const QgsPointXY mySrcPoint2 = mCPMatrix[r][c];
753 const QgsPointXY mySrcPoint3 = mCPMatrix[r][c + 1];
754
755 const QgsPointXY mySrcApprox( ( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 );
756 if ( !mCPLegalMatrix[r][c - 1] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r][c + 1] )
757 {
758 // There was an error earlier in transform, just abort
759 return false;
760 }
761 try
762 {
763 const QgsPointXY myDestApprox = ct.transform( mySrcApprox, Qgis::TransformDirection::Reverse );
764 const double mySqrDist = myDestApprox.sqrDist( myDestPoint );
765 if ( mySqrDist > mSqrTolerance )
766 {
767 return false;
768 }
769 }
770 catch ( QgsCsException &e )
771 {
772 Q_UNUSED( e )
773 // Caught an error in transform
774 return false;
775 }
776 }
777 }
778 return true;
779}
780
782
783
785{
786 switch ( precision )
787 {
788 case Approximate:
789 return tr( "Approximate" );
790 case Exact:
791 return tr( "Exact" );
792 }
793 return QStringLiteral( "Unknown" );
794}
795
796QgsRasterBlock *QgsRasterProjector::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
797{
798 QgsDebugMsgLevel( QStringLiteral( "extent:\n%1" ).arg( extent.toString() ), 4 );
799 QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ), 4 );
800 if ( !mInput )
801 {
802 QgsDebugMsgLevel( QStringLiteral( "Input not set" ), 4 );
803 return new QgsRasterBlock();
804 }
805
806 if ( feedback && feedback->isCanceled() )
807 return new QgsRasterBlock();
808
809 if ( ! mSrcCRS.isValid() || ! mDestCRS.isValid() || mSrcCRS == mDestCRS )
810 {
811 QgsDebugMsgLevel( QStringLiteral( "No projection necessary" ), 4 );
812 return mInput->block( bandNo, extent, width, height, feedback );
813 }
814
816 const QgsCoordinateTransform inverseCt = mSrcDatumTransform != -1 || mDestDatumTransform != -1 ?
817 QgsCoordinateTransform( mDestCRS, mSrcCRS, mDestDatumTransform, mSrcDatumTransform ) : QgsCoordinateTransform( mDestCRS, mSrcCRS, mTransformContext ) ;
819
820 ProjectorData pd( extent, width, height, mInput, inverseCt, mPrecision, feedback );
821
822 if ( feedback && feedback->isCanceled() )
823 return new QgsRasterBlock();
824
825 QgsDebugMsgLevel( QStringLiteral( "srcExtent:\n%1" ).arg( pd.srcExtent().toString() ), 4 );
826 QgsDebugMsgLevel( QStringLiteral( "srcCols = %1 srcRows = %2" ).arg( pd.srcCols() ).arg( pd.srcRows() ), 4 );
827
828 // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers
829 if ( pd.srcRows() <= 0 || pd.srcCols() <= 0 )
830 {
831 QgsDebugMsgLevel( QStringLiteral( "Zero srcRows or srcCols" ), 4 );
832 return new QgsRasterBlock();
833 }
834
835 std::unique_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNo, pd.srcExtent(), pd.srcCols(), pd.srcRows(), feedback ) );
836 const QgsRasterBlock *input = inputBlock.get();
837 if ( !input || input->isEmpty() )
838 {
839 QgsDebugError( QStringLiteral( "No raster data!" ) );
840 return new QgsRasterBlock();
841 }
842
843 const qgssize pixelSize = static_cast<qgssize>( QgsRasterBlock::typeSize( mInput->dataType( bandNo ) ) );
844
845 auto outputBlock = std::make_unique< QgsRasterBlock >( input->dataType(), width, height );
846 QgsRasterBlock *output = outputBlock.get();
847
848 if ( input->hasNoDataValue() )
849 {
850 output->setNoDataValue( input->noDataValue() );
851 }
852 if ( !output->isValid() )
853 {
854 QgsDebugError( QStringLiteral( "Cannot create block" ) );
855 return outputBlock.release();
856 }
857
858 // set output to no data, it should be fast
859 output->setIsNoData();
860
861 // No data: because isNoData()/setIsNoData() is slow with respect to simple memcpy,
862 // we use if only if necessary:
863 // 1) no data value exists (numerical) -> memcpy, not necessary isNoData()/setIsNoData()
864 // 2) no data value does not exist but it may contain no data (numerical no data bitmap)
865 // -> must use isNoData()/setIsNoData()
866 // 3) no data are not used (no no data value, no no data bitmap) -> simple memcpy
867 // 4) image - simple memcpy
868
869 // To copy no data values stored in bitmaps we have to use isNoData()/setIsNoData(),
870 // we cannot fill output block with no data because we use memcpy for data, not setValue().
871 const bool doNoData = !QgsRasterBlock::typeIsNumeric( input->dataType() ) && input->hasNoData() && !input->hasNoDataValue();
872
873 int srcRow, srcCol;
874 for ( int i = 0; i < height; ++i )
875 {
876 if ( feedback && feedback->isCanceled() )
877 break;
878 for ( int j = 0; j < width; ++j )
879 {
880 const bool inside = pd.srcRowCol( i, j, &srcRow, &srcCol );
881 if ( !inside ) continue; // we have everything set to no data
882
883 const qgssize srcIndex = static_cast< qgssize >( srcRow ) * pd.srcCols() + srcCol;
884
885 // isNoData() may be slow so we check doNoData first
886 if ( doNoData && input->isNoData( srcRow, srcCol ) )
887 {
888 continue;
889 }
890
891 const qgssize destIndex = static_cast< qgssize >( i ) * width + j;
892 const char *srcBits = input->constBits( srcIndex );
893 char *destBits = output->bits( destIndex );
894 if ( !srcBits )
895 {
896 // QgsDebugError( QStringLiteral( "Cannot get input block data: row = %1 col = %2" ).arg( i ).arg( j ) );
897 continue;
898 }
899 if ( !destBits )
900 {
901 // QgsDebugError( QStringLiteral( "Cannot set output block data: srcRow = %1 srcCol = %2" ).arg( srcRow ).arg( srcCol ) );
902 continue;
903 }
904 memcpy( destBits, srcBits, pixelSize );
905 output->setIsData( i, j );
906 }
907 }
908
909 return outputBlock.release();
910}
911
912bool QgsRasterProjector::destExtentSize( const QgsRectangle &srcExtent, int srcXSize, int srcYSize,
913 QgsRectangle &destExtent, int &destXSize, int &destYSize )
914{
915 if ( srcExtent.isEmpty() || srcXSize <= 0 || srcYSize <= 0 )
916 {
917 return false;
918 }
919
921 const QgsCoordinateTransform ct = mSrcDatumTransform != -1 || mDestDatumTransform != -1 ?
922 QgsCoordinateTransform( mSrcCRS, mDestCRS, mSrcDatumTransform, mDestDatumTransform ) : QgsCoordinateTransform( mSrcCRS, mDestCRS, mTransformContext ) ;
924
925 return extentSize( ct, srcExtent, srcXSize, srcYSize, destExtent, destXSize, destYSize );
926}
927
929 const QgsRectangle &srcExtent, int srcXSize, int srcYSize,
930 QgsRectangle &destExtent, int &destXSize, int &destYSize )
931{
932 if ( srcExtent.isEmpty() || srcXSize <= 0 || srcYSize <= 0 )
933 {
934 return false;
935 }
936
937 QgsCoordinateTransform extentTransform = ct;
938 extentTransform.setBallparkTransformsAreAppropriate( true );
939
940 destExtent = extentTransform.transformBoundingBox( srcExtent );
941
942 // We reproject pixel rectangle from 9 points matrix of source extent, of course, it gives
943 // bigger xRes,yRes than reprojected edges (envelope)
944 constexpr int steps = 3;
945 const double srcXStep = srcExtent.width() / steps;
946 const double srcYStep = srcExtent.height() / steps;
947 const double srcXRes = srcExtent.width() / srcXSize;
948 const double srcYRes = srcExtent.height() / srcYSize;
949 double destXRes = std::numeric_limits<double>::max();
950 double destYRes = std::numeric_limits<double>::max();
951 double maxXRes = 0;
952 double maxYRes = 0;
953
954 for ( int i = 0; i < steps; i++ )
955 {
956 const double x = srcExtent.xMinimum() + i * srcXStep;
957 for ( int j = 0; j < steps; j++ )
958 {
959 const double y = srcExtent.yMinimum() + j * srcYStep;
960 const QgsRectangle srcRectangle( x - srcXRes / 2, y - srcYRes / 2, x + srcXRes / 2, y + srcYRes / 2 );
961 try
962 {
963 const QgsRectangle destRectangle = extentTransform.transformBoundingBox( srcRectangle );
964 if ( destRectangle.width() > 0 )
965 {
966 destXRes = std::min( destXRes, destRectangle.width() );
967 if ( destRectangle.width() > maxXRes )
968 maxXRes = destRectangle.width();
969 }
970 if ( destRectangle.height() > 0 )
971 {
972 destYRes = std::min( destYRes, destRectangle.height() );
973 if ( destRectangle.height() > maxYRes )
974 maxYRes = destRectangle.height();
975 }
976 }
977 catch ( QgsCsException & )
978 {
979
980 }
981 }
982 }
983
984 // Limit resolution to 1/10th of the maximum resolution to avoid issues
985 // with for example WebMercator at high northings that result in very small
986 // latitude differences.
987 if ( destXRes < 0.1 * maxXRes )
988 {
989 destXRes = 0.1 * maxXRes;
990 }
991 if ( destYRes < 0.1 * maxYRes )
992 {
993 destYRes = 0.1 * maxYRes;
994 }
995 if ( destXRes == 0 || destExtent.width() / destXRes > std::numeric_limits<int>::max() )
996 return false;
997 if ( destYRes == 0 || destExtent.height() / destYRes > std::numeric_limits<int>::max() )
998 return false;
999
1000 destXSize = std::max( 1, static_cast< int >( destExtent.width() / destXRes ) );
1001 destYSize = std::max( 1, static_cast< int >( destExtent.height() / destYRes ) );
1002
1003 return true;
1004}
1005
@ ProviderHintCanPerformProviderResampling
Provider can perform resampling (to be opposed to post rendering resampling).
Definition qgis.h:4891
@ Size
Original data source size (and thus resolution) is known, it is not always available,...
Definition qgis.h:4855
DataType
Raster data types.
Definition qgis.h:372
@ UnknownDataType
Unknown or unspecified type.
Definition qgis.h:373
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Handles coordinate transforms between two coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
Represents a 2D point.
Definition qgspointxy.h:60
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:186
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:129
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:119
Feedback object tailored for raster block reading.
Raster data container.
bool isValid() const
Returns true if the block is valid (correctly filled with data).
static bool typeIsNumeric(Qgis::DataType type)
Returns true if a data type is numeric.
char * bits(int row, int column)
Returns a pointer to block data.
static int typeSize(Qgis::DataType dataType)
Returns the size in bytes for the specified dataType.
void setIsData(int row, int column)
Remove no data flag on pixel.
void setNoDataValue(double noDataValue)
Sets cell value that will be considered as "no data".
bool setIsNoData(int row, int column)
Set no data on pixel.
QgsRectangle extent() const override=0
Returns the extent of the layer.
virtual Qgis::RasterProviderCapabilities providerCapabilities() const
Returns flags containing the supported capabilities of the data provider.
Base class for processing filters like renderers, reprojector, resampler etc.
virtual Qgis::RasterInterfaceCapabilities capabilities() const
Returns the capabilities supported by the interface.
virtual int xSize() const
Gets raster size.
QgsRasterInterface(QgsRasterInterface *input=nullptr)
QgsRasterInterface * mInput
virtual int ySize() const
virtual QgsRectangle extent() const
Gets the extent of the interface.
virtual QgsRasterInterface * input() const
Current input.
QgsRasterProjector * clone() const override
Clone itself, create deep copy.
bool destExtentSize(const QgsRectangle &srcExtent, int srcXSize, int srcYSize, QgsRectangle &destExtent, int &destXSize, int &destYSize)
Calculate destination extent and size from source extent and size.
static QString precisionLabel(Precision precision)
Qgis::DataType dataType(int bandNo) const override
Returns data type for the band specified by number.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
static bool extentSize(const QgsCoordinateTransform &ct, const QgsRectangle &srcExtent, int srcXSize, int srcYSize, QgsRectangle &destExtent, int &destXSize, int &destYSize)
Calculate destination extent and size from source extent and size.
Precision
Precision defines if each pixel is reprojected or approximate reprojection based on an approximation ...
@ Exact
Exact, precise but slow.
@ Approximate
Approximate (default), fast but possibly inaccurate.
Precision precision() const
int bandCount() const override
Gets number of bands.
Q_DECL_DEPRECATED void setCrs(const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS, int srcDatumTransform=-1, int destDatumTransform=-1)
Sets the source and destination CRS.
A rectangle specified with double values.
double xMinimum
double yMinimum
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7170
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:7142
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57