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