Skip to content

Commit d48c178

Browse files
authoredMay 20, 2020
Fix misalignment of rasters with RPC (fixes #35796) (#36385)
* Fix misalignment of rasters with RPC (fixes #35796) By default GDAL would assume that the imagery has zero elevation - if that is not the case, the image would not be shown in the correct location. Fortunately we can pass RPC_HEIGHT to GDAL to use given value as the fixed elevation for the whole image. We try to use HEIGHT_OFF coefficient ("Geodetic Height Offset") as an estimate for elevation (it seems that ENVI software does that as well). In the future we may want to use also RPC_DEM for an even more precise georeferencing. https://gdal.org/development/rfc/rfc22_rpc.html * Update code to use the new fuction GDALAutoCreateWarpedVRTEx() This function will be available in GDAL >= 3.2 so we have a local copy for the time being for older versions of GDAL.
1 parent 7053b78 commit d48c178

File tree

3 files changed

+237
-3
lines changed

3 files changed

+237
-3
lines changed
 

‎src/core/providers/gdal/qgsgdalprovider.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2670,9 +2670,18 @@ void QgsGdalProvider::initBaseDataset()
26702670
{
26712671
QgsLogger::warning( QStringLiteral( "Creating Warped VRT." ) );
26722672

2673-
mGdalDataset =
2674-
GDALAutoCreateWarpedVRT( mGdalBaseDataset, nullptr, nullptr,
2675-
GRA_NearestNeighbour, 0.2, nullptr );
2673+
if ( GDALGetMetadata( mGdalBaseDataset, "RPC" ) )
2674+
{
2675+
mGdalDataset =
2676+
QgsGdalUtils::rpcAwareAutoCreateWarpedVrt( mGdalBaseDataset, nullptr, nullptr,
2677+
GRA_NearestNeighbour, 0.2, nullptr );
2678+
}
2679+
else
2680+
{
2681+
mGdalDataset =
2682+
GDALAutoCreateWarpedVRT( mGdalBaseDataset, nullptr, nullptr,
2683+
GRA_NearestNeighbour, 0.2, nullptr );
2684+
}
26762685

26772686
if ( !mGdalDataset )
26782687
{

‎src/core/qgsgdalutils.cpp

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#define CPL_SUPRESS_CPLUSPLUS //#spellok
2020
#include "gdal.h"
21+
#include "gdalwarper.h"
2122
#include "cpl_string.h"
2223

2324
#include <QString>
@@ -277,3 +278,211 @@ QString QgsGdalUtils::validateCreationOptionsFormat( const QStringList &createOp
277278
return QStringLiteral( "Failed GDALValidateCreationOptions() test" );
278279
return QString();
279280
}
281+
282+
#if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,3,0)
283+
// GDAL < 2.3 does not come with GDALWarpInitDefaultBandMapping and GDALWarpInitNoDataReal
284+
// in the public API so we define them here for rpcAwareAutoCreateWarpedVrt()
285+
286+
static void GDALWarpInitDefaultBandMapping( GDALWarpOptions *psOptionsIn, int nBandCount )
287+
{
288+
if ( psOptionsIn->nBandCount != 0 ) { return; }
289+
290+
psOptionsIn->nBandCount = nBandCount;
291+
292+
psOptionsIn->panSrcBands = static_cast<int *>(
293+
CPLMalloc( sizeof( int ) * psOptionsIn->nBandCount ) );
294+
psOptionsIn->panDstBands = static_cast<int *>(
295+
CPLMalloc( sizeof( int ) * psOptionsIn->nBandCount ) );
296+
297+
for ( int i = 0; i < psOptionsIn->nBandCount; i++ )
298+
{
299+
psOptionsIn->panSrcBands[i] = i + 1;
300+
psOptionsIn->panDstBands[i] = i + 1;
301+
}
302+
}
303+
304+
static void InitNoData( int nBandCount, double **ppdNoDataReal, double dDataReal )
305+
{
306+
if ( nBandCount <= 0 ) { return; }
307+
if ( *ppdNoDataReal != nullptr ) { return; }
308+
309+
*ppdNoDataReal = static_cast<double *>(
310+
CPLMalloc( sizeof( double ) * nBandCount ) );
311+
312+
for ( int i = 0; i < nBandCount; ++i )
313+
{
314+
( *ppdNoDataReal )[i] = dDataReal;
315+
}
316+
}
317+
318+
static void GDALWarpInitNoDataReal( GDALWarpOptions *psOptionsIn, double dNoDataReal )
319+
{
320+
InitNoData( psOptionsIn->nBandCount, &psOptionsIn->padfDstNoDataReal, dNoDataReal );
321+
InitNoData( psOptionsIn->nBandCount, &psOptionsIn->padfSrcNoDataReal, dNoDataReal );
322+
// older GDAL also requires imaginary values to be set
323+
InitNoData( psOptionsIn->nBandCount, &psOptionsIn->padfDstNoDataImag, 0 );
324+
InitNoData( psOptionsIn->nBandCount, &psOptionsIn->padfSrcNoDataImag, 0 );
325+
}
326+
#endif
327+
328+
#if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,2,0)
329+
330+
GDALDatasetH GDALAutoCreateWarpedVRTEx( GDALDatasetH hSrcDS, const char *pszSrcWKT, const char *pszDstWKT, GDALResampleAlg eResampleAlg,
331+
double dfMaxError, const GDALWarpOptions *psOptionsIn, char **papszTransformerOptions )
332+
{
333+
VALIDATE_POINTER1( hSrcDS, "GDALAutoCreateWarpedVRT", nullptr );
334+
335+
/* -------------------------------------------------------------------- */
336+
/* Populate the warp options. */
337+
/* -------------------------------------------------------------------- */
338+
GDALWarpOptions *psWO = nullptr;
339+
if ( psOptionsIn != nullptr )
340+
psWO = GDALCloneWarpOptions( psOptionsIn );
341+
else
342+
psWO = GDALCreateWarpOptions();
343+
344+
psWO->eResampleAlg = eResampleAlg;
345+
346+
psWO->hSrcDS = hSrcDS;
347+
348+
GDALWarpInitDefaultBandMapping( psWO, GDALGetRasterCount( hSrcDS ) );
349+
350+
/* -------------------------------------------------------------------- */
351+
/* Setup no data values */
352+
/* -------------------------------------------------------------------- */
353+
for ( int i = 0; i < psWO->nBandCount; i++ )
354+
{
355+
GDALRasterBandH rasterBand = GDALGetRasterBand( psWO->hSrcDS, psWO->panSrcBands[i] );
356+
357+
int hasNoDataValue;
358+
double noDataValue = GDALGetRasterNoDataValue( rasterBand, &hasNoDataValue );
359+
360+
if ( hasNoDataValue )
361+
{
362+
// Check if the nodata value is out of range
363+
int bClamped = FALSE;
364+
int bRounded = FALSE;
365+
CPL_IGNORE_RET_VAL(
366+
GDALAdjustValueToDataType( GDALGetRasterDataType( rasterBand ),
367+
noDataValue, &bClamped, &bRounded ) );
368+
if ( !bClamped )
369+
{
370+
GDALWarpInitNoDataReal( psWO, -1e10 );
371+
372+
psWO->padfSrcNoDataReal[i] = noDataValue;
373+
psWO->padfDstNoDataReal[i] = noDataValue;
374+
}
375+
}
376+
}
377+
378+
if ( psWO->padfDstNoDataReal != nullptr )
379+
{
380+
if ( CSLFetchNameValue( psWO->papszWarpOptions, "INIT_DEST" ) == nullptr )
381+
{
382+
psWO->papszWarpOptions =
383+
CSLSetNameValue( psWO->papszWarpOptions, "INIT_DEST", "NO_DATA" );
384+
}
385+
}
386+
387+
/* -------------------------------------------------------------------- */
388+
/* Create the transformer. */
389+
/* -------------------------------------------------------------------- */
390+
psWO->pfnTransformer = GDALGenImgProjTransform;
391+
392+
char **papszOptions = nullptr;
393+
if ( pszSrcWKT != nullptr )
394+
papszOptions = CSLSetNameValue( papszOptions, "SRC_SRS", pszSrcWKT );
395+
if ( pszDstWKT != nullptr )
396+
papszOptions = CSLSetNameValue( papszOptions, "DST_SRS", pszDstWKT );
397+
papszOptions = CSLMerge( papszOptions, papszTransformerOptions );
398+
psWO->pTransformerArg =
399+
GDALCreateGenImgProjTransformer2( psWO->hSrcDS, nullptr,
400+
papszOptions );
401+
CSLDestroy( papszOptions );
402+
403+
if ( psWO->pTransformerArg == nullptr )
404+
{
405+
GDALDestroyWarpOptions( psWO );
406+
return nullptr;
407+
}
408+
409+
/* -------------------------------------------------------------------- */
410+
/* Figure out the desired output bounds and resolution. */
411+
/* -------------------------------------------------------------------- */
412+
double adfDstGeoTransform[6] = { 0.0 };
413+
int nDstPixels = 0;
414+
int nDstLines = 0;
415+
CPLErr eErr =
416+
GDALSuggestedWarpOutput( hSrcDS, psWO->pfnTransformer,
417+
psWO->pTransformerArg,
418+
adfDstGeoTransform, &nDstPixels, &nDstLines );
419+
if ( eErr != CE_None )
420+
{
421+
GDALDestroyTransformer( psWO->pTransformerArg );
422+
GDALDestroyWarpOptions( psWO );
423+
return nullptr;
424+
}
425+
426+
/* -------------------------------------------------------------------- */
427+
/* Update the transformer to include an output geotransform */
428+
/* back to pixel/line coordinates. */
429+
/* */
430+
/* -------------------------------------------------------------------- */
431+
GDALSetGenImgProjTransformerDstGeoTransform(
432+
psWO->pTransformerArg, adfDstGeoTransform );
433+
434+
/* -------------------------------------------------------------------- */
435+
/* Do we want to apply an approximating transformation? */
436+
/* -------------------------------------------------------------------- */
437+
if ( dfMaxError > 0.0 )
438+
{
439+
psWO->pTransformerArg =
440+
GDALCreateApproxTransformer( psWO->pfnTransformer,
441+
psWO->pTransformerArg,
442+
dfMaxError );
443+
psWO->pfnTransformer = GDALApproxTransform;
444+
GDALApproxTransformerOwnsSubtransformer( psWO->pTransformerArg, TRUE );
445+
}
446+
447+
/* -------------------------------------------------------------------- */
448+
/* Create the VRT file. */
449+
/* -------------------------------------------------------------------- */
450+
GDALDatasetH hDstDS
451+
= GDALCreateWarpedVRT( hSrcDS, nDstPixels, nDstLines,
452+
adfDstGeoTransform, psWO );
453+
454+
GDALDestroyWarpOptions( psWO );
455+
456+
if ( pszDstWKT != nullptr )
457+
GDALSetProjection( hDstDS, pszDstWKT );
458+
else if ( pszSrcWKT != nullptr )
459+
GDALSetProjection( hDstDS, pszSrcWKT );
460+
else if ( GDALGetGCPCount( hSrcDS ) > 0 )
461+
GDALSetProjection( hDstDS, GDALGetGCPProjection( hSrcDS ) );
462+
else
463+
GDALSetProjection( hDstDS, GDALGetProjectionRef( hSrcDS ) );
464+
465+
return hDstDS;
466+
}
467+
#endif
468+
469+
470+
GDALDatasetH QgsGdalUtils::rpcAwareAutoCreateWarpedVrt(
471+
GDALDatasetH hSrcDS,
472+
const char *pszSrcWKT,
473+
const char *pszDstWKT,
474+
GDALResampleAlg eResampleAlg,
475+
double dfMaxError,
476+
const GDALWarpOptions *psOptionsIn )
477+
{
478+
char **opts = nullptr;
479+
if ( GDALGetMetadata( hSrcDS, "RPC" ) )
480+
{
481+
// well-behaved RPC should have height offset a good value for RPC_HEIGHT
482+
const char *heightOffStr = GDALGetMetadataItem( hSrcDS, "HEIGHT_OFF", "RPC" );
483+
if ( heightOffStr )
484+
opts = CSLAddNameValue( opts, "RPC_HEIGHT", heightOffStr );
485+
}
486+
487+
return GDALAutoCreateWarpedVRTEx( hSrcDS, pszSrcWKT, pszDstWKT, eResampleAlg, dfMaxError, psOptionsIn, opts );
488+
}

‎src/core/qgsgdalutils.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ class CORE_EXPORT QgsGdalUtils
100100
*/
101101
static gdal::dataset_unique_ptr imageToMemoryDataset( const QImage &image );
102102

103+
/**
104+
* This is a copy of GDALAutoCreateWarpedVRT optimized for imagery using RPC georeferencing
105+
* that also sets RPC_HEIGHT in GDALCreateGenImgProjTransformer2 based on HEIGHT_OFF.
106+
* By default GDAL would assume that the imagery has zero elevation - if that is not the case,
107+
* the image would not be shown in the correct location.
108+
*
109+
* \since QGIS 3.14
110+
*/
111+
static GDALDatasetH rpcAwareAutoCreateWarpedVrt(
112+
GDALDatasetH hSrcDS,
113+
const char *pszSrcWKT,
114+
const char *pszDstWKT,
115+
GDALResampleAlg eResampleAlg,
116+
double dfMaxError,
117+
const GDALWarpOptions *psOptionsIn );
118+
103119
friend class TestQgsGdalUtils;
104120
};
105121

0 commit comments

Comments
 (0)
Please sign in to comment.