QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 = 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 )
542 return false;
543 if ( *srcRow < 0 )
544 return false;
545 if ( *srcCol >= mSrcCols )
546 return false;
547 if ( *srcCol < 0 )
548 return false;
549
550 return true;
551}
552
553bool ProjectorData::approximateSrcRowCol( int destRow, int destCol, int *srcRow, int *srcCol )
554{
555 const int myMatrixRow = matrixRow( destRow );
556 const int myMatrixCol = matrixCol( destCol );
557
558 if ( myMatrixRow > mHelperTopRow )
559 {
560 // TODO: make it more robust (for random, not sequential reading)
561 nextHelper();
562 }
563
564 const double myDestY = mDestExtent.yMaximum() - ( destRow + 0.5 ) * mDestYRes;
565
566 // See the schema in javax.media.jai.WarpGrid doc (but up side down)
567 // TODO: use some kind of cache of values which can be reused
568 double myDestXMin, myDestYMin, myDestXMax, myDestYMax;
569
570 destPointOnCPMatrix( myMatrixRow + 1, myMatrixCol, &myDestXMin, &myDestYMin );
571 destPointOnCPMatrix( myMatrixRow, myMatrixCol + 1, &myDestXMax, &myDestYMax );
572
573 const double yfrac = ( myDestY - myDestYMin ) / ( myDestYMax - myDestYMin );
574
575 const QgsPointXY &myTop = pHelperTop[destCol];
576 const QgsPointXY &myBot = pHelperBottom[destCol];
577
578 // Warning: this is very SLOW compared to the following code!:
579 //double mySrcX = myBot.x() + (myTop.x() - myBot.x()) * yfrac;
580 //double mySrcY = myBot.y() + (myTop.y() - myBot.y()) * yfrac;
581
582 const double tx = myTop.x();
583 const double ty = myTop.y();
584 const double bx = myBot.x();
585 const double by = myBot.y();
586 const double mySrcX = bx + ( tx - bx ) * yfrac;
587 const double mySrcY = by + ( ty - by ) * yfrac;
588
589 if ( !mExtent.contains( mySrcX, mySrcY ) )
590 {
591 return false;
592 }
593
594 // TODO: check again cell selection (coor is in the middle)
595
596 *srcRow = static_cast< int >( std::floor( ( mSrcExtent.yMaximum() - mySrcY ) / mSrcYRes ) );
597 *srcCol = static_cast< int >( std::floor( ( mySrcX - mSrcExtent.xMinimum() ) / mSrcXRes ) );
598
599 // For now silently correct limits to avoid crashes
600 // TODO: review
601 // should not happen
602 if ( *srcRow >= mSrcRows )
603 return false;
604 if ( *srcRow < 0 )
605 return false;
606 if ( *srcCol >= mSrcCols )
607 return false;
608 if ( *srcCol < 0 )
609 return false;
610
611 return true;
612}
613
614void ProjectorData::insertRows( const QgsCoordinateTransform &ct )
615{
616 for ( int r = 0; r < mCPRows - 1; r++ )
617 {
618 QList<QgsPointXY> myRow;
619 QList<bool> myLegalRow;
620 myRow.reserve( mCPCols );
621 myLegalRow.reserve( mCPCols );
622 for ( int c = 0; c < mCPCols; ++c )
623 {
624 myRow.append( QgsPointXY() );
625 myLegalRow.append( false );
626 }
627 QgsDebugMsgLevel( u"insert new row at %1"_s.arg( 1 + r * 2 ), 3 );
628 mCPMatrix.insert( 1 + r * 2, myRow );
629 mCPLegalMatrix.insert( 1 + r * 2, myLegalRow );
630 }
631 mCPRows += mCPRows - 1;
632 for ( int r = 1; r < mCPRows - 1; r += 2 )
633 {
634 calcRow( r, ct );
635 }
636}
637
638void ProjectorData::insertCols( const QgsCoordinateTransform &ct )
639{
640 for ( int r = 0; r < mCPRows; r++ )
641 {
642 for ( int c = 0; c < mCPCols - 1; c++ )
643 {
644 mCPMatrix[r].insert( 1 + c * 2, QgsPointXY() );
645 mCPLegalMatrix[r].insert( 1 + c * 2, false );
646 }
647 }
648 mCPCols += mCPCols - 1;
649 for ( int c = 1; c < mCPCols - 1; c += 2 )
650 {
651 calcCol( c, ct );
652 }
653}
654
655void ProjectorData::calcCP( int row, int col, const QgsCoordinateTransform &ct )
656{
657 double myDestX, myDestY;
658 destPointOnCPMatrix( row, col, &myDestX, &myDestY );
659 const QgsPointXY myDestPoint( myDestX, myDestY );
660 try
661 {
662 if ( ct.isValid() )
663 {
664 mCPMatrix[row][col] = ct.transform( myDestPoint );
665 mCPLegalMatrix[row][col] = true;
666 }
667 else
668 {
669 mCPLegalMatrix[row][col] = false;
670 }
671 }
672 catch ( QgsCsException &e )
673 {
674 Q_UNUSED( e )
675 // Caught an error in transform
676 mCPLegalMatrix[row][col] = false;
677 }
678}
679
680bool ProjectorData::calcRow( int row, const QgsCoordinateTransform &ct )
681{
682 QgsDebugMsgLevel( u"theRow = %1"_s.arg( row ), 3 );
683 for ( int i = 0; i < mCPCols; i++ )
684 {
685 calcCP( row, i, ct );
686 }
687
688 return true;
689}
690
691bool ProjectorData::calcCol( int col, const QgsCoordinateTransform &ct )
692{
693 QgsDebugMsgLevel( u"theCol = %1"_s.arg( col ), 3 );
694 for ( int i = 0; i < mCPRows; i++ )
695 {
696 calcCP( i, col, ct );
697 }
698
699 return true;
700}
701
702bool ProjectorData::checkCols( const QgsCoordinateTransform &ct ) const
703{
704 if ( !ct.isValid() )
705 {
706 return false;
707 }
708
709 for ( int c = 0; c < mCPCols; c++ )
710 {
711 for ( int r = 1; r < mCPRows - 1; r += 2 )
712 {
713 double myDestX, myDestY;
714 destPointOnCPMatrix( r, c, &myDestX, &myDestY );
715 const QgsPointXY myDestPoint( myDestX, myDestY );
716
717 const QgsPointXY mySrcPoint1 = mCPMatrix[r - 1][c];
718 const QgsPointXY mySrcPoint2 = mCPMatrix[r][c];
719 const QgsPointXY mySrcPoint3 = mCPMatrix[r + 1][c];
720
721 const QgsPointXY mySrcApprox( ( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 );
722 if ( !mCPLegalMatrix[r - 1][c] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r + 1][c] )
723 {
724 // There was an error earlier in transform, just abort
725 return false;
726 }
727 try
728 {
729 const QgsPointXY myDestApprox = ct.transform( mySrcApprox, Qgis::TransformDirection::Reverse );
730 const double mySqrDist = myDestApprox.sqrDist( myDestPoint );
731 if ( mySqrDist > mSqrTolerance )
732 {
733 return false;
734 }
735 }
736 catch ( QgsCsException &e )
737 {
738 Q_UNUSED( e )
739 // Caught an error in transform
740 return false;
741 }
742 }
743 }
744 return true;
745}
746
747bool ProjectorData::checkRows( const QgsCoordinateTransform &ct ) const
748{
749 if ( !ct.isValid() )
750 {
751 return false;
752 }
753
754 for ( int r = 0; r < mCPRows; r++ )
755 {
756 for ( int c = 1; c < mCPCols - 1; c += 2 )
757 {
758 double myDestX, myDestY;
759 destPointOnCPMatrix( r, c, &myDestX, &myDestY );
760
761 const QgsPointXY myDestPoint( myDestX, myDestY );
762 const QgsPointXY mySrcPoint1 = mCPMatrix[r][c - 1];
763 const QgsPointXY mySrcPoint2 = mCPMatrix[r][c];
764 const QgsPointXY mySrcPoint3 = mCPMatrix[r][c + 1];
765
766 const QgsPointXY mySrcApprox( ( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 );
767 if ( !mCPLegalMatrix[r][c - 1] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r][c + 1] )
768 {
769 // There was an error earlier in transform, just abort
770 return false;
771 }
772 try
773 {
774 const QgsPointXY myDestApprox = ct.transform( mySrcApprox, Qgis::TransformDirection::Reverse );
775 const double mySqrDist = myDestApprox.sqrDist( myDestPoint );
776 if ( mySqrDist > mSqrTolerance )
777 {
778 return false;
779 }
780 }
781 catch ( QgsCsException &e )
782 {
783 Q_UNUSED( e )
784 // Caught an error in transform
785 return false;
786 }
787 }
788 }
789 return true;
790}
791
793
794
796{
797 switch ( precision )
798 {
799 case Approximate:
800 return tr( "Approximate" );
801 case Exact:
802 return tr( "Exact" );
803 }
804 return u"Unknown"_s;
805}
806
807QgsRasterBlock *QgsRasterProjector::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
808{
809 QgsDebugMsgLevel( u"extent:\n%1"_s.arg( extent.toString() ), 4 );
810 QgsDebugMsgLevel( u"width = %1 height = %2"_s.arg( width ).arg( height ), 4 );
811 if ( !mInput )
812 {
813 QgsDebugMsgLevel( u"Input not set"_s, 4 );
814 return new QgsRasterBlock();
815 }
816
817 if ( feedback && feedback->isCanceled() )
818 return new QgsRasterBlock();
819
820 if ( !mSrcCRS.isValid() || !mDestCRS.isValid() || mSrcCRS == mDestCRS )
821 {
822 QgsDebugMsgLevel( u"No projection necessary"_s, 4 );
823 return mInput->block( bandNo, extent, width, height, feedback );
824 }
825
827 const QgsCoordinateTransform inverseCt = mSrcDatumTransform != -1 || mDestDatumTransform != -1 ? QgsCoordinateTransform( mDestCRS, mSrcCRS, mDestDatumTransform, mSrcDatumTransform )
828 : QgsCoordinateTransform( mDestCRS, mSrcCRS, mTransformContext );
830
831 ProjectorData pd( extent, width, height, mInput, inverseCt, mPrecision, feedback );
832
833 if ( feedback && feedback->isCanceled() )
834 return new QgsRasterBlock();
835
836 QgsDebugMsgLevel( u"srcExtent:\n%1"_s.arg( pd.srcExtent().toString() ), 4 );
837 QgsDebugMsgLevel( u"srcCols = %1 srcRows = %2"_s.arg( pd.srcCols() ).arg( pd.srcRows() ), 4 );
838
839 // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers
840 if ( pd.srcRows() <= 0 || pd.srcCols() <= 0 )
841 {
842 QgsDebugMsgLevel( u"Zero srcRows or srcCols"_s, 4 );
843 return new QgsRasterBlock();
844 }
845
846 std::unique_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNo, pd.srcExtent(), pd.srcCols(), pd.srcRows(), feedback ) );
847 const QgsRasterBlock *input = inputBlock.get();
848 if ( !input || input->isEmpty() )
849 {
850 QgsDebugError( u"No raster data!"_s );
851 return new QgsRasterBlock();
852 }
853
854 const qgssize pixelSize = static_cast<qgssize>( QgsRasterBlock::typeSize( mInput->dataType( bandNo ) ) );
855
856 auto outputBlock = std::make_unique< QgsRasterBlock >( input->dataType(), width, height );
857 QgsRasterBlock *output = outputBlock.get();
858
859 if ( input->hasNoDataValue() )
860 {
861 output->setNoDataValue( input->noDataValue() );
862 }
863 if ( !output->isValid() )
864 {
865 QgsDebugError( u"Cannot create block"_s );
866 return outputBlock.release();
867 }
868
869 // set output to no data, it should be fast
870 output->setIsNoData();
871
872 // No data: because isNoData()/setIsNoData() is slow with respect to simple memcpy,
873 // we use if only if necessary:
874 // 1) no data value exists (numerical) -> memcpy, not necessary isNoData()/setIsNoData()
875 // 2) no data value does not exist but it may contain no data (numerical no data bitmap)
876 // -> must use isNoData()/setIsNoData()
877 // 3) no data are not used (no no data value, no no data bitmap) -> simple memcpy
878 // 4) image - simple memcpy
879
880 // To copy no data values stored in bitmaps we have to use isNoData()/setIsNoData(),
881 // we cannot fill output block with no data because we use memcpy for data, not setValue().
882 const bool doNoData = !QgsRasterBlock::typeIsNumeric( input->dataType() ) && input->hasNoData() && !input->hasNoDataValue();
883
884 int srcRow, srcCol;
885 for ( int i = 0; i < height; ++i )
886 {
887 if ( feedback && feedback->isCanceled() )
888 break;
889 for ( int j = 0; j < width; ++j )
890 {
891 const bool inside = pd.srcRowCol( i, j, &srcRow, &srcCol );
892 if ( !inside )
893 continue; // we have everything set to no data
894
895 const qgssize srcIndex = static_cast< qgssize >( srcRow ) * pd.srcCols() + srcCol;
896
897 // isNoData() may be slow so we check doNoData first
898 if ( doNoData && input->isNoData( srcRow, srcCol ) )
899 {
900 continue;
901 }
902
903 const qgssize destIndex = static_cast< qgssize >( i ) * width + j;
904 const char *srcBits = input->constBits( srcIndex );
905 char *destBits = output->bits( destIndex );
906 if ( !srcBits )
907 {
908 // QgsDebugError( u"Cannot get input block data: row = %1 col = %2"_s.arg( i ).arg( j ) );
909 continue;
910 }
911 if ( !destBits )
912 {
913 // QgsDebugError( u"Cannot set output block data: srcRow = %1 srcCol = %2"_s.arg( srcRow ).arg( srcCol ) );
914 continue;
915 }
916 memcpy( destBits, srcBits, pixelSize );
917 output->setIsData( i, j );
918 }
919 }
920
921 return outputBlock.release();
922}
923
924bool QgsRasterProjector::destExtentSize( const QgsRectangle &srcExtent, int srcXSize, int srcYSize, QgsRectangle &destExtent, int &destXSize, int &destYSize )
925{
926 if ( srcExtent.isEmpty() || srcXSize <= 0 || srcYSize <= 0 )
927 {
928 return false;
929 }
930
932 const QgsCoordinateTransform ct = mSrcDatumTransform != -1 || mDestDatumTransform != -1 ? QgsCoordinateTransform( mSrcCRS, mDestCRS, mSrcDatumTransform, mDestDatumTransform )
933 : QgsCoordinateTransform( mSrcCRS, mDestCRS, mTransformContext );
935
936 return extentSize( ct, srcExtent, srcXSize, srcYSize, destExtent, destXSize, destYSize );
937}
938
939bool QgsRasterProjector::extentSize( const QgsCoordinateTransform &ct, const QgsRectangle &srcExtent, int srcXSize, int srcYSize, QgsRectangle &destExtent, int &destXSize, int &destYSize )
940{
941 if ( srcExtent.isEmpty() || srcXSize <= 0 || srcYSize <= 0 )
942 {
943 return false;
944 }
945
946 QgsCoordinateTransform extentTransform = ct;
947 extentTransform.setBallparkTransformsAreAppropriate( true );
948
949 destExtent = extentTransform.transformBoundingBox( srcExtent );
950
951 // We reproject pixel rectangle from 9 points matrix of source extent, of course, it gives
952 // bigger xRes,yRes than reprojected edges (envelope)
953 constexpr int steps = 3;
954 const double srcXStep = srcExtent.width() / steps;
955 const double srcYStep = srcExtent.height() / steps;
956 const double srcXRes = srcExtent.width() / srcXSize;
957 const double srcYRes = srcExtent.height() / srcYSize;
958 double destXRes = std::numeric_limits<double>::max();
959 double destYRes = std::numeric_limits<double>::max();
960 double maxXRes = 0;
961 double maxYRes = 0;
962
963 for ( int i = 0; i < steps; i++ )
964 {
965 const double x = srcExtent.xMinimum() + i * srcXStep;
966 for ( int j = 0; j < steps; j++ )
967 {
968 const double y = srcExtent.yMinimum() + j * srcYStep;
969 const QgsRectangle srcRectangle( x - srcXRes / 2, y - srcYRes / 2, x + srcXRes / 2, y + srcYRes / 2 );
970 try
971 {
972 const QgsRectangle destRectangle = extentTransform.transformBoundingBox( srcRectangle );
973 if ( destRectangle.width() > 0 )
974 {
975 destXRes = std::min( destXRes, destRectangle.width() );
976 if ( destRectangle.width() > maxXRes )
977 maxXRes = destRectangle.width();
978 }
979 if ( destRectangle.height() > 0 )
980 {
981 destYRes = std::min( destYRes, destRectangle.height() );
982 if ( destRectangle.height() > maxYRes )
983 maxYRes = destRectangle.height();
984 }
985 }
986 catch ( QgsCsException & )
987 {}
988 }
989 }
990
991 // Limit resolution to 1/10th of the maximum resolution to avoid issues
992 // with for example WebMercator at high northings that result in very small
993 // latitude differences.
994 if ( destXRes < 0.1 * maxXRes )
995 {
996 destXRes = 0.1 * maxXRes;
997 }
998 if ( destYRes < 0.1 * maxYRes )
999 {
1000 destYRes = 0.1 * maxYRes;
1001 }
1002 if ( destXRes == 0 || destExtent.width() / destXRes > std::numeric_limits<int>::max() )
1003 return false;
1004 if ( destYRes == 0 || destExtent.height() / destYRes > std::numeric_limits<int>::max() )
1005 return false;
1006
1007 destXSize = std::max( 1, static_cast< int >( destExtent.width() / destXRes ) );
1008 destYSize = std::max( 1, static_cast< int >( destExtent.height() / destYRes ) );
1009
1010 return true;
1011}
@ ProviderHintCanPerformProviderResampling
Provider can perform resampling (to be opposed to post rendering resampling).
Definition qgis.h:5048
@ Size
Original data source size (and thus resolution) is known, it is not always available,...
Definition qgis.h:5012
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:2766
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
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:132
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:122
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:7504
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:7485
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7503
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59