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