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