gdal/frmts/postgisraster/postgisrasterrasterband.cpp

776 строки
27 KiB
C++

/***********************************************************************
* File : postgisrasterrasterband.cpp
* Project: PostGIS Raster driver
* Purpose: GDAL RasterBand implementation for PostGIS Raster driver
* Author: Jorge Arevalo, jorge.arevalo@deimos-space.com
* jorgearevalo@libregis.org
*
* Author: David Zwarg, dzwarg@azavea.com
*
* Last changes: $Id$
*
***********************************************************************
* Copyright (c) 2009 - 2013, Jorge Arevalo, David Zwarg
* Copyright (c) 2013-2018, Even Rouault <even.rouault at spatialys.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**********************************************************************/
#include "postgisraster.h"
/**
* \brief Constructor.
*
* nBand it is just necessary for overview band creation
*/
PostGISRasterRasterBand::PostGISRasterRasterBand(PostGISRasterDataset *poDSIn,
int nBandIn,
GDALDataType eDataTypeIn,
GBool bNoDataValueSetIn,
double dfNodata)
: VRTSourcedRasterBand(poDSIn, nBandIn), pszSchema(poDSIn->pszSchema),
pszTable(poDSIn->pszTable), pszColumn(poDSIn->pszColumn)
{
/* Basic properties */
poDS = poDSIn;
nBand = nBandIn;
eDataType = eDataTypeIn;
m_bNoDataValueSet = bNoDataValueSetIn;
m_dfNoDataValue = dfNodata;
nRasterXSize = poDS->GetRasterXSize();
nRasterYSize = poDS->GetRasterYSize();
/*******************************************************************
* Finally, set the block size. We apply the same logic than in VRT
* driver.
*
* We limit the size of a block with MAX_BLOCK_SIZE here to prevent
* arrangements of just one big tile.
*
* This value is just used in case we only have 1 tile in the
* table. Otherwise, the reading operations are performed by the
* sources, not the PostGISRasterBand object itself.
******************************************************************/
nBlockXSize = atoi(CPLGetConfigOption(
"PR_BLOCKXSIZE",
CPLSPrintf("%d", MIN(MAX_BLOCK_SIZE, this->nRasterXSize))));
nBlockYSize = atoi(CPLGetConfigOption(
"PR_BLOCKYSIZE",
CPLSPrintf("%d", MIN(MAX_BLOCK_SIZE, this->nRasterYSize))));
#ifdef DEBUG_VERBOSE
CPLDebug("PostGIS_Raster",
"PostGISRasterRasterBand constructor: Band size: (%d X %d)",
nRasterXSize, nRasterYSize);
CPLDebug("PostGIS_Raster",
"PostGISRasterRasterBand::Constructor: "
"Block size (%dx%d)",
this->nBlockXSize, this->nBlockYSize);
#endif
}
/***********************************************
* \brief: Band destructor
***********************************************/
PostGISRasterRasterBand::~PostGISRasterRasterBand()
{
}
/********************************************************
* \brief Set nodata value to a buffer
********************************************************/
void PostGISRasterRasterBand::NullBuffer(void *pData, int nBufXSize,
int nBufYSize, GDALDataType eBufType,
int nPixelSpace, int nLineSpace)
{
int j;
for (j = 0; j < nBufYSize; j++)
{
double dfVal = 0.0;
if (m_bNoDataValueSet)
dfVal = m_dfNoDataValue;
GDALCopyWords(&dfVal, GDT_Float64, 0,
static_cast<GByte *>(pData) + j * nLineSpace, eBufType,
nPixelSpace, nBufXSize);
}
}
/********************************************************
* \brief SortTilesByPKID
********************************************************/
static int SortTilesByPKID(const void *a, const void *b)
{
const PostGISRasterTileDataset *pa =
*static_cast<const PostGISRasterTileDataset *const *>(a);
const PostGISRasterTileDataset *pb =
*static_cast<const PostGISRasterTileDataset *const *>(b);
return strcmp(pa->GetPKID(), pb->GetPKID());
}
/**
* Read/write a region of image data for this band.
*
* This method allows reading a region of a PostGISRasterBand into a buffer.
* The write support is still under development
*
* The function fetches all the raster data that intersects with the region
* provided, and store the data in the GDAL cache.
*
* It automatically takes care of data type translation if the data type
* (eBufType) of the buffer is different than that of the
* PostGISRasterRasterBand.
*
* The nPixelSpace and nLineSpace parameters allow reading into FROM various
* organization of buffers.
*
* @param eRWFlag Either GF_Read to read a region of data (GF_Write, to write
* a region of data, yet not supported)
*
* @param nXOff The pixel offset to the top left corner of the region of the
* band to be accessed. This would be zero to start FROM the left side.
*
* @param nYOff The line offset to the top left corner of the region of the band
* to be accessed. This would be zero to start FROM the top.
*
* @param nXSize The width of the region of the band to be accessed in pixels.
*
* @param nYSize The height of the region of the band to be accessed in lines.
*
* @param pData The buffer into which the data should be read, or FROM which it
* should be written. This buffer must contain at least
* nBufXSize * nBufYSize * nBandCount words of type eBufType. It is organized in
* left to right,top to bottom pixel order. Spacing is controlled by the
* nPixelSpace, and nLineSpace parameters.
*
* @param nBufXSize the width of the buffer image into which the desired region
* is to be read, or FROM which it is to be written.
*
* @param nBufYSize the height of the buffer image into which the desired region
* is to be read, or FROM which it is to be written.
*
* @param eBufType the type of the pixel values in the pData data buffer. The
* pixel values will automatically be translated to/FROM the
* PostGISRasterRasterBand data type as needed.
*
* @param nPixelSpace The byte offset FROM the start of one pixel value in pData
* to the start of the next pixel value within a scanline. If defaulted (0) the
* size of the datatype eBufType is used.
*
* @param nLineSpace The byte offset FROM the start of one scanline in pData to
* the start of the next. If defaulted (0) the size of the datatype
* eBufType * nBufXSize is used.
*
* @return CE_Failure if the access fails, otherwise CE_None.
*/
CPLErr PostGISRasterRasterBand::IRasterIO(
GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
{
/**
* TODO: Write support not implemented yet
**/
if (eRWFlag == GF_Write)
{
ReportError(CE_Failure, CPLE_NotSupported,
"Writing through PostGIS Raster band not supported yet");
return CE_Failure;
}
/*******************************************************************
* Do we have overviews that would be appropriate to satisfy this
* request?
******************************************************************/
if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0)
{
if (OverviewRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
nBufXSize, nBufYSize, eBufType, nPixelSpace,
nLineSpace, psExtraArg) == CE_None)
return CE_None;
}
PostGISRasterDataset *poRDS = cpl::down_cast<PostGISRasterDataset *>(poDS);
int bSameWindowAsOtherBand =
(nXOff == poRDS->nXOffPrev && nYOff == poRDS->nYOffPrev &&
nXSize == poRDS->nXSizePrev && nYSize == poRDS->nYSizePrev);
poRDS->nXOffPrev = nXOff;
poRDS->nYOffPrev = nYOff;
poRDS->nXSizePrev = nXSize;
poRDS->nYSizePrev = nYSize;
/* Logic to determine if bands are read in order 1, 2, ... N */
/* If so, then use multi-band caching, otherwise do just single band caching
*/
if (poRDS->bAssumeMultiBandReadPattern)
{
if (nBand != poRDS->nNextExpectedBand)
{
CPLDebug("PostGIS_Raster", "Disabling multi-band caching since "
"band access pattern does not match");
poRDS->bAssumeMultiBandReadPattern = false;
poRDS->nNextExpectedBand = 1;
}
else
{
poRDS->nNextExpectedBand++;
if (poRDS->nNextExpectedBand > poRDS->GetRasterCount())
poRDS->nNextExpectedBand = 1;
}
}
else
{
if (nBand == poRDS->nNextExpectedBand)
{
poRDS->nNextExpectedBand++;
if (poRDS->nNextExpectedBand > poRDS->GetRasterCount())
{
CPLDebug("PostGIS_Raster", "Re-enabling multi-band caching");
poRDS->bAssumeMultiBandReadPattern = true;
poRDS->nNextExpectedBand = 1;
}
}
}
#ifdef DEBUG_VERBOSE
CPLDebug("PostGIS_Raster",
"PostGISRasterRasterBand::IRasterIO: "
"nBand = %d, nXOff = %d, nYOff = %d, nXSize = %d, nYSize = %d, "
"nBufXSize = %d, nBufYSize = %d",
nBand, nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
#endif
/*******************************************************************
* Several tiles: we first look in all our sources caches. Missing
* blocks are queried
******************************************************************/
double adfProjWin[8];
int nFeatureCount = 0;
CPLRectObj sAoi;
poRDS->PolygonFromCoords(nXOff, nYOff, nXOff + nXSize, nYOff + nYSize,
adfProjWin);
// (p[6], p[7]) is the minimum (x, y), and (p[2], p[3]) the max
sAoi.minx = adfProjWin[6];
sAoi.maxx = adfProjWin[2];
if (adfProjWin[7] < adfProjWin[3])
{
sAoi.miny = adfProjWin[7];
sAoi.maxy = adfProjWin[3];
}
else
{
sAoi.maxy = adfProjWin[7];
sAoi.miny = adfProjWin[3];
}
#ifdef DEBUG_VERBOSE
CPLDebug("PostGIS_Raster",
"PostGISRasterRasterBand::IRasterIO: "
"Intersection box: (%f, %f) - (%f, %f)",
sAoi.minx, sAoi.miny, sAoi.maxx, sAoi.maxy);
#endif
if (poRDS->hQuadTree == nullptr)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Could not read metadata index.");
return CE_Failure;
}
NullBuffer(pData, nBufXSize, nBufYSize, eBufType,
static_cast<int>(nPixelSpace), static_cast<int>(nLineSpace));
if (poRDS->bBuildQuadTreeDynamically && !bSameWindowAsOtherBand)
{
if (!(poRDS->LoadSources(nXOff, nYOff, nXSize, nYSize, nBand)))
return CE_Failure;
}
// Matching sources, to avoid a dumb for loop over the sources
PostGISRasterTileDataset **papsMatchingTiles =
reinterpret_cast<PostGISRasterTileDataset **>(
CPLQuadTreeSearch(poRDS->hQuadTree, &sAoi, &nFeatureCount));
// No blocks found. This is not an error (the raster may have holes)
if (nFeatureCount == 0)
{
CPLFree(papsMatchingTiles);
return CE_None;
}
int i;
/**
* We need to store the max, min coords for the missing tiles in
* any place. This is as good as any other
**/
sAoi.minx = 0.0;
sAoi.miny = 0.0;
sAoi.maxx = 0.0;
sAoi.maxy = 0.0;
GIntBig nMemoryRequiredForTiles = 0;
CPLString osIDsToFetch;
int nTilesToFetch = 0;
int nBandDataTypeSize = GDALGetDataTypeSize(eDataType) / 8;
// Loop just over the intersecting sources
for (i = 0; i < nFeatureCount; i++)
{
PostGISRasterTileDataset *poTile = papsMatchingTiles[i];
PostGISRasterTileRasterBand *poTileBand =
cpl::down_cast<PostGISRasterTileRasterBand *>(
poTile->GetRasterBand(nBand));
nMemoryRequiredForTiles +=
poTileBand->GetXSize() * poTileBand->GetYSize() * nBandDataTypeSize;
// Missing tile: we'll need to query for it
if (!poTileBand->IsCached())
{
// If we have a PKID, add the tile PKID to the list
if (poTile->pszPKID != nullptr)
{
if (!osIDsToFetch.empty())
osIDsToFetch += ",";
osIDsToFetch += "'";
osIDsToFetch += poTile->pszPKID;
osIDsToFetch += "'";
}
double dfTileMinX, dfTileMinY, dfTileMaxX, dfTileMaxY;
poTile->GetExtent(&dfTileMinX, &dfTileMinY, &dfTileMaxX,
&dfTileMaxY);
/**
* We keep the general max and min values of all the missing
* tiles, to raise a query that intersect just that area.
*
* TODO: In case of just a few tiles and very separated,
* this strategy is clearly suboptimal. We'll get our
* missing tiles, but with a lot of other not needed tiles.
*
* A possible optimization will be to simply rely on the
* I/O method of the source (must be implemented), in case
* we have minus than a reasonable amount of tiles missing.
* Another criteria to decide would be how separated the
* tiles are. Two queries for just two adjacent tiles is
* also a dumb strategy.
**/
if (nTilesToFetch == 0)
{
sAoi.minx = dfTileMinX;
sAoi.miny = dfTileMinY;
sAoi.maxx = dfTileMaxX;
sAoi.maxy = dfTileMaxY;
}
else
{
if (dfTileMinX < sAoi.minx)
sAoi.minx = dfTileMinX;
if (dfTileMinY < sAoi.miny)
sAoi.miny = dfTileMinY;
if (dfTileMaxX > sAoi.maxx)
sAoi.maxx = dfTileMaxX;
if (dfTileMaxY > sAoi.maxy)
sAoi.maxy = dfTileMaxY;
}
nTilesToFetch++;
}
}
/* Determine caching strategy */
bool bAllBandCaching = false;
if (nTilesToFetch > 0)
{
GIntBig nCacheMax = GDALGetCacheMax64();
if (nMemoryRequiredForTiles > nCacheMax)
{
CPLDebug("PostGIS_Raster",
"For best performance, the block cache should be able to "
"store " CPL_FRMT_GIB
" bytes for the tiles of the requested window, "
"but it is only " CPL_FRMT_GIB " byte large",
nMemoryRequiredForTiles, nCacheMax);
nTilesToFetch = 0;
}
if (poRDS->GetRasterCount() > 1 && poRDS->bAssumeMultiBandReadPattern)
{
GIntBig nMemoryRequiredForTilesAllBands =
nMemoryRequiredForTiles * poRDS->GetRasterCount();
if (nMemoryRequiredForTilesAllBands <= nCacheMax)
{
bAllBandCaching = true;
}
else
{
CPLDebug("PostGIS_Raster",
"Caching only this band, but not all bands. "
"Cache should be " CPL_FRMT_GIB " byte large for that",
nMemoryRequiredForTilesAllBands);
}
}
}
// Raise a query for missing tiles and cache them
if (nTilesToFetch > 0)
{
/**
* There are several options here, to raise the query.
* - Get all the tiles which PKID is in a list of missing
* PKIDs.
* - Get all the tiles that intersect a polygon constructed
* based on the (min - max) values calculated before.
* - Get all the tiles with upper left pixel included in the
* range (min - max) calculated before.
*
* The first option is the most efficient one when a PKID exists.
* After that, the second one is the most efficient one when a
* spatial index exists.
* The third one is the only one available when neither a PKID or
*spatial index exist.
**/
CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
CPLString osWHERE;
if (!osIDsToFetch.empty() &&
(poRDS->bIsFastPK || !(poRDS->HasSpatialIndex())))
{
if (nTilesToFetch < poRDS->m_nTiles ||
poRDS->bBuildQuadTreeDynamically)
{
osWHERE += poRDS->pszPrimaryKeyName;
osWHERE += " IN (";
osWHERE += osIDsToFetch;
osWHERE += ")";
}
}
else
{
if (poRDS->HasSpatialIndex())
{
osWHERE += CPLSPrintf(
"%s && "
"ST_GeomFromText('POLYGON((%.18f %.18f,%.18f %.18f,%.18f "
"%.18f,%.18f %.18f,%.18f %.18f))')",
osColumnI.c_str(), adfProjWin[0], adfProjWin[1],
adfProjWin[2], adfProjWin[3], adfProjWin[4], adfProjWin[5],
adfProjWin[6], adfProjWin[7], adfProjWin[0], adfProjWin[1]);
}
else
{
#define EPS 1e-5
osWHERE += CPLSPrintf(
"ST_UpperLeftX(%s)"
" BETWEEN %f AND %f AND ST_UpperLeftY(%s) BETWEEN "
"%f AND %f",
osColumnI.c_str(), sAoi.minx - EPS, sAoi.maxx + EPS,
osColumnI.c_str(), sAoi.miny - EPS, sAoi.maxy + EPS);
}
}
if (poRDS->pszWhere != nullptr)
{
if (!osWHERE.empty())
osWHERE += " AND ";
osWHERE += "(";
osWHERE += poRDS->pszWhere;
osWHERE += ")";
}
bool bCanUseClientSide = true;
if (poRDS->eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
{
bCanUseClientSide =
poRDS->CanUseClientSideOutDB(bAllBandCaching, nBand, osWHERE);
}
CPLString osRasterToFetch;
if (bAllBandCaching)
osRasterToFetch = osColumnI;
else
osRasterToFetch.Printf("ST_Band(%s, %d)", osColumnI.c_str(), nBand);
if (poRDS->eOutDBResolution == OutDBResolution::SERVER_SIDE ||
!bCanUseClientSide)
{
osRasterToFetch =
"encode(ST_AsBinary(" + osRasterToFetch + ",TRUE),'hex')";
}
CPLString osCommand;
osCommand.Printf("SELECT %s, ST_Metadata(%s), %s FROM %s.%s",
(poRDS->GetPrimaryKeyRef()) ? poRDS->GetPrimaryKeyRef()
: "NULL",
osColumnI.c_str(), osRasterToFetch.c_str(),
osSchemaI.c_str(), osTableI.c_str());
if (!osWHERE.empty())
{
osCommand += " WHERE " + osWHERE;
}
PGresult *poResult = PQexec(poRDS->poConn, osCommand.c_str());
#ifdef DEBUG_QUERY
CPLDebug("PostGIS_Raster",
"PostGISRasterRasterBand::IRasterIO(): Query = \"%s\" --> "
"number of rows = %d",
osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
#endif
if (poResult == nullptr ||
PQresultStatus(poResult) != PGRES_TUPLES_OK ||
PQntuples(poResult) < 0)
{
if (poResult)
PQclear(poResult);
CPLError(CE_Failure, CPLE_AppDefined,
"PostGISRasterRasterBand::IRasterIO(): %s",
PQerrorMessage(poRDS->poConn));
// Free the object that holds pointers to matching tiles
CPLFree(papsMatchingTiles);
return CE_Failure;
}
/**
* No data. Return the buffer filled with nodata values
**/
else if (PQntuples(poResult) == 0)
{
PQclear(poResult);
// Free the object that holds pointers to matching tiles
CPLFree(papsMatchingTiles);
return CE_None;
}
/**
* Ok, we loop over the results
**/
int nTuples = PQntuples(poResult);
for (i = 0; i < nTuples; i++)
{
const char *pszPKID = PQgetvalue(poResult, i, 0);
const char *pszMetadata = PQgetvalue(poResult, i, 1);
const char *pszRaster = PQgetvalue(poResult, i, 2);
poRDS->CacheTile(pszMetadata, pszRaster, pszPKID, nBand,
bAllBandCaching);
} // All tiles have been added to cache
PQclear(poResult);
} // End missing tiles
/* -------------------------------------------------------------------- */
/* Overlay each source in turn over top this. */
/* -------------------------------------------------------------------- */
CPLErr eErr = CE_None;
/* Sort tiles by ascending PKID, so that the draw order is deterministic. */
if (poRDS->GetPrimaryKeyRef() != nullptr)
{
qsort(papsMatchingTiles, nFeatureCount,
sizeof(PostGISRasterTileDataset *), SortTilesByPKID);
}
for (i = 0; i < nFeatureCount && eErr == CE_None; i++)
{
PostGISRasterTileDataset *poTile = papsMatchingTiles[i];
PostGISRasterTileRasterBand *poTileBand =
cpl::down_cast<PostGISRasterTileRasterBand *>(
poTile->GetRasterBand(nBand));
eErr = poTileBand->poSource->RasterIO(
eDataType, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
nBufYSize, eBufType, nPixelSpace, nLineSpace, nullptr);
}
// Free the object that holds pointers to matching tiles
CPLFree(papsMatchingTiles);
return eErr;
}
/**
* \brief Set the no data value for this band.
* Parameters:
* - double: The nodata value
* Returns:
* - CE_None.
*/
CPLErr PostGISRasterRasterBand::SetNoDataValue(double dfNewValue)
{
m_dfNoDataValue = dfNewValue;
return CE_None;
}
/**
* \brief Fetch the no data value for this band.
* Parameters:
* - int *: pointer to a boolean to use to indicate if a value is actually
* associated with this layer. May be NULL (default).
* Returns:
* - double: the nodata value for this band.
*/
double PostGISRasterRasterBand::GetNoDataValue(int *pbSuccess)
{
if (pbSuccess != nullptr)
*pbSuccess = m_bNoDataValueSet;
return m_dfNoDataValue;
}
/***************************************************
* \brief Return the number of overview layers available
***************************************************/
int PostGISRasterRasterBand::GetOverviewCount()
{
PostGISRasterDataset *poRDS = cpl::down_cast<PostGISRasterDataset *>(poDS);
return poRDS->GetOverviewCount();
}
/**********************************************************
* \brief Fetch overview raster band object
**********************************************************/
GDALRasterBand *PostGISRasterRasterBand::GetOverview(int i)
{
if (i < 0 || i >= GetOverviewCount())
return nullptr;
PostGISRasterDataset *poRDS = cpl::down_cast<PostGISRasterDataset *>(poDS);
PostGISRasterDataset *poOverviewDS = poRDS->GetOverviewDS(i);
if (poOverviewDS->nBands == 0)
{
if (!poOverviewDS->SetRasterProperties(nullptr) ||
poOverviewDS->GetRasterCount() != poRDS->GetRasterCount())
{
CPLDebug("PostGIS_Raster",
"Request for overview %d of band %d failed", i, nBand);
return nullptr;
}
}
return poOverviewDS->GetRasterBand(nBand);
}
/**
* \brief How should this band be interpreted as color?
* GCI_Undefined is returned when the format doesn't know anything about the
* color interpretation.
**/
GDALColorInterp PostGISRasterRasterBand::GetColorInterpretation()
{
if (poDS->GetRasterCount() == 1)
{
m_eColorInterp = GCI_GrayIndex;
}
else if (poDS->GetRasterCount() == 3)
{
if (nBand == 1)
m_eColorInterp = GCI_RedBand;
else if (nBand == 2)
m_eColorInterp = GCI_GreenBand;
else if (nBand == 3)
m_eColorInterp = GCI_BlueBand;
else
m_eColorInterp = GCI_Undefined;
}
else
{
m_eColorInterp = GCI_Undefined;
}
return m_eColorInterp;
}
/************************************************************************/
/* GetMinimum() */
/************************************************************************/
double PostGISRasterRasterBand::GetMinimum(int *pbSuccess)
{
PostGISRasterDataset *poRDS = cpl::down_cast<PostGISRasterDataset *>(poDS);
if (poRDS->bBuildQuadTreeDynamically && poRDS->m_nTiles == 0)
{
if (pbSuccess)
*pbSuccess = FALSE;
return 0.0;
}
return VRTSourcedRasterBand::GetMaximum(pbSuccess);
}
/************************************************************************/
/* GetMaximum() */
/************************************************************************/
double PostGISRasterRasterBand::GetMaximum(int *pbSuccess)
{
PostGISRasterDataset *poRDS = cpl::down_cast<PostGISRasterDataset *>(poDS);
if (poRDS->bBuildQuadTreeDynamically && poRDS->m_nTiles == 0)
{
if (pbSuccess)
*pbSuccess = FALSE;
return 0.0;
}
return VRTSourcedRasterBand::GetMaximum(pbSuccess);
}
/************************************************************************/
/* ComputeRasterMinMax() */
/************************************************************************/
CPLErr PostGISRasterRasterBand::ComputeRasterMinMax(int bApproxOK,
double *adfMinMax)
{
if (nRasterXSize < 1024 && nRasterYSize < 1024)
return VRTSourcedRasterBand::ComputeRasterMinMax(bApproxOK, adfMinMax);
int nOverviewCount = GetOverviewCount();
for (int i = 0; i < nOverviewCount; i++)
{
auto poOverview = GetOverview(i);
if (poOverview->GetXSize() < 1024 && poOverview->GetYSize() < 1024)
return poOverview->ComputeRasterMinMax(bApproxOK, adfMinMax);
}
return CE_Failure;
}