914 lines
33 KiB
C++
914 lines
33 KiB
C++
/******************************************************************************
|
|
*
|
|
* Project: GDAL Utilities
|
|
* Purpose: Command line application to build overviews.
|
|
* Author: Frank Warmerdam, warmerdam@pobox.com
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2000, Frank Warmerdam
|
|
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
****************************************************************************/
|
|
|
|
#include "cpl_string.h"
|
|
#include "gdal_version.h"
|
|
#include "gdal_priv.h"
|
|
#include "commonutils.h"
|
|
#include "vrtdataset.h"
|
|
#include "vrt_priv.h"
|
|
#include "gdalargumentparser.h"
|
|
|
|
#include <algorithm>
|
|
|
|
/************************************************************************/
|
|
/* GDALAddoErrorHandler() */
|
|
/************************************************************************/
|
|
|
|
class GDALError
|
|
{
|
|
public:
|
|
CPLErr m_eErr;
|
|
CPLErrorNum m_errNum;
|
|
CPLString m_osMsg;
|
|
|
|
explicit GDALError(CPLErr eErr = CE_None, CPLErrorNum errNum = CPLE_None,
|
|
const char *pszMsg = "")
|
|
: m_eErr(eErr), m_errNum(errNum), m_osMsg(pszMsg ? pszMsg : "")
|
|
{
|
|
}
|
|
};
|
|
|
|
std::vector<GDALError> aoErrors;
|
|
|
|
static void CPL_STDCALL GDALAddoErrorHandler(CPLErr eErr, CPLErrorNum errNum,
|
|
const char *pszMsg)
|
|
{
|
|
aoErrors.push_back(GDALError(eErr, errNum, pszMsg));
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PartialRefresh() */
|
|
/************************************************************************/
|
|
|
|
static bool PartialRefresh(GDALDataset *poDS,
|
|
const std::vector<int> &anOvrIndices, int nBandCount,
|
|
const int *panBandList, const char *pszResampling,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
GDALProgressFunc pfnProgress, void *pProgressArg)
|
|
{
|
|
std::vector<int> anBandList;
|
|
if (nBandCount == 0)
|
|
{
|
|
for (int i = 0; i < poDS->GetRasterCount(); ++i)
|
|
anBandList.push_back(i + 1);
|
|
nBandCount = poDS->GetRasterCount();
|
|
panBandList = anBandList.data();
|
|
}
|
|
|
|
int nOvCount = 0;
|
|
for (int i = 0; i < nBandCount; ++i)
|
|
{
|
|
auto poSrcBand = poDS->GetRasterBand(panBandList[i]);
|
|
if (i == 0)
|
|
nOvCount = poSrcBand->GetOverviewCount();
|
|
else if (nOvCount != poSrcBand->GetOverviewCount())
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Not same number of overviews on all bands");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::vector<GDALRasterBand *> apoSrcBands;
|
|
std::vector<GDALRasterBand **> apapoOverviewBands;
|
|
for (int i = 0; i < nBandCount; ++i)
|
|
{
|
|
auto poSrcBand = poDS->GetRasterBand(panBandList[i]);
|
|
apoSrcBands.push_back(poSrcBand);
|
|
apapoOverviewBands.push_back(static_cast<GDALRasterBand **>(
|
|
CPLMalloc(sizeof(GDALRasterBand *) * anOvrIndices.size())));
|
|
int j = 0;
|
|
for (int nOvrIdx : anOvrIndices)
|
|
{
|
|
apapoOverviewBands[i][j] = poSrcBand->GetOverview(nOvrIdx);
|
|
++j;
|
|
}
|
|
}
|
|
|
|
CPLStringList aosOptions;
|
|
aosOptions.SetNameValue("XOFF", CPLSPrintf("%d", nXOff));
|
|
aosOptions.SetNameValue("YOFF", CPLSPrintf("%d", nYOff));
|
|
aosOptions.SetNameValue("XSIZE", CPLSPrintf("%d", nXSize));
|
|
aosOptions.SetNameValue("YSIZE", CPLSPrintf("%d", nYSize));
|
|
bool bOK = GDALRegenerateOverviewsMultiBand(
|
|
nBandCount, apoSrcBands.data(),
|
|
static_cast<int>(anOvrIndices.size()),
|
|
apapoOverviewBands.data(), pszResampling, pfnProgress,
|
|
pProgressArg, aosOptions.List()) == CE_None;
|
|
for (auto papoOverviewBands : apapoOverviewBands)
|
|
CPLFree(papoOverviewBands);
|
|
return bOK;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetOvrIndices() */
|
|
/************************************************************************/
|
|
|
|
static bool GetOvrIndices(GDALDataset *poDS, int nLevelCount,
|
|
const int *panLevels, bool bMinSizeSpecified,
|
|
int nMinSize, std::vector<int> &anOvrIndices)
|
|
{
|
|
auto poBand = poDS->GetRasterBand(1);
|
|
if (!poBand)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Dataset has no bands");
|
|
return false;
|
|
}
|
|
const int nOvCount = poBand->GetOverviewCount();
|
|
if (nOvCount == 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Dataset has no overviews");
|
|
return false;
|
|
}
|
|
|
|
if (nLevelCount == 0)
|
|
{
|
|
if (!bMinSizeSpecified)
|
|
{
|
|
for (int i = 0; i < nOvCount; ++i)
|
|
anOvrIndices.push_back(i);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < nOvCount; i++)
|
|
{
|
|
GDALRasterBand *poOverview = poBand->GetOverview(i);
|
|
if (poOverview == nullptr)
|
|
continue;
|
|
if (poOverview->GetXSize() >= nMinSize ||
|
|
poOverview->GetYSize() >= nMinSize)
|
|
{
|
|
anOvrIndices.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < nLevelCount; ++i)
|
|
{
|
|
const int nLevel = panLevels[i];
|
|
int nIdx = -1;
|
|
for (int j = 0; j < nOvCount; j++)
|
|
{
|
|
GDALRasterBand *poOverview = poBand->GetOverview(j);
|
|
if (poOverview == nullptr)
|
|
continue;
|
|
|
|
int nOvFactor = GDALComputeOvFactor(
|
|
poOverview->GetXSize(), poBand->GetXSize(),
|
|
poOverview->GetYSize(), poBand->GetYSize());
|
|
|
|
if (nOvFactor == nLevel ||
|
|
nOvFactor == GDALOvLevelAdjust2(nLevel, poBand->GetXSize(),
|
|
poBand->GetYSize()))
|
|
{
|
|
nIdx = j;
|
|
break;
|
|
}
|
|
}
|
|
if (nIdx < 0)
|
|
{
|
|
CPLError(
|
|
CE_Failure, CPLE_AppDefined,
|
|
"Cannot find overview level with subsampling factor of %d",
|
|
nLevel);
|
|
return false;
|
|
}
|
|
anOvrIndices.push_back(nIdx);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PartialRefreshFromSourceTimestamp() */
|
|
/************************************************************************/
|
|
|
|
static bool PartialRefreshFromSourceTimestamp(
|
|
GDALDataset *poDS, const char *pszResampling, int nLevelCount,
|
|
const int *panLevels, int nBandCount, const int *panBandList,
|
|
bool bMinSizeSpecified, int nMinSize, GDALProgressFunc pfnProgress,
|
|
void *pProgressArg)
|
|
{
|
|
std::vector<int> anOvrIndices;
|
|
if (!GetOvrIndices(poDS, nLevelCount, panLevels, bMinSizeSpecified,
|
|
nMinSize, anOvrIndices))
|
|
return false;
|
|
|
|
VSIStatBufL sStatVRTOvr;
|
|
std::string osVRTOvr(std::string(poDS->GetDescription()) + ".ovr");
|
|
if (VSIStatL(osVRTOvr.c_str(), &sStatVRTOvr) != 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s\n",
|
|
osVRTOvr.c_str());
|
|
return false;
|
|
}
|
|
if (sStatVRTOvr.st_mtime == 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot get modification time of %s\n", osVRTOvr.c_str());
|
|
return false;
|
|
}
|
|
|
|
std::vector<GTISourceDesc> regions;
|
|
|
|
double dfTotalPixels = 0;
|
|
|
|
if (dynamic_cast<VRTDataset *>(poDS))
|
|
{
|
|
auto poVRTBand =
|
|
dynamic_cast<VRTSourcedRasterBand *>(poDS->GetRasterBand(1));
|
|
if (!poVRTBand)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Band is not a VRTSourcedRasterBand");
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < poVRTBand->nSources; ++i)
|
|
{
|
|
auto poSource =
|
|
dynamic_cast<VRTSimpleSource *>(poVRTBand->papoSources[i]);
|
|
if (poSource)
|
|
{
|
|
VSIStatBufL sStatSource;
|
|
if (VSIStatL(poSource->GetSourceDatasetName().c_str(),
|
|
&sStatSource) == 0)
|
|
{
|
|
if (sStatSource.st_mtime > sStatVRTOvr.st_mtime)
|
|
{
|
|
double dfXOff, dfYOff, dfXSize, dfYSize;
|
|
poSource->GetDstWindow(dfXOff, dfYOff, dfXSize,
|
|
dfYSize);
|
|
constexpr double EPS = 1e-8;
|
|
int nXOff = static_cast<int>(dfXOff + EPS);
|
|
int nYOff = static_cast<int>(dfYOff + EPS);
|
|
int nXSize = static_cast<int>(dfXSize + 0.5);
|
|
int nYSize = static_cast<int>(dfYSize + 0.5);
|
|
if (nXOff > poDS->GetRasterXSize() ||
|
|
nYOff > poDS->GetRasterYSize() || nXSize <= 0 ||
|
|
nYSize <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (nXOff < 0)
|
|
{
|
|
nXSize += nXOff;
|
|
nXOff = 0;
|
|
}
|
|
if (nXOff > poDS->GetRasterXSize() - nXSize)
|
|
{
|
|
nXSize = poDS->GetRasterXSize() - nXOff;
|
|
}
|
|
if (nYOff < 0)
|
|
{
|
|
nYSize += nYOff;
|
|
nYOff = 0;
|
|
}
|
|
if (nYOff > poDS->GetRasterYSize() - nYSize)
|
|
{
|
|
nYSize = poDS->GetRasterYSize() - nYOff;
|
|
}
|
|
|
|
dfTotalPixels += static_cast<double>(nXSize) * nYSize;
|
|
GTISourceDesc region;
|
|
region.osFilename = poSource->GetSourceDatasetName();
|
|
region.nDstXOff = nXOff;
|
|
region.nDstYOff = nYOff;
|
|
region.nDstXSize = nXSize;
|
|
region.nDstYSize = nYSize;
|
|
regions.push_back(std::move(region));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef GTI_DRIVER_DISABLED_OR_PLUGIN
|
|
else if (poDS->GetDriver() &&
|
|
EQUAL(poDS->GetDriver()->GetDescription(), "GTI"))
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"--partial-refresh-from-source-timestamp only works on a GTI "
|
|
"dataset if the GTI driver is not built as a plugin, "
|
|
"but in core library");
|
|
return false;
|
|
}
|
|
#else
|
|
else if (auto poGTIDS = GDALDatasetCastToGTIDataset(poDS))
|
|
{
|
|
regions = GTIGetSourcesMoreRecentThan(poGTIDS, sStatVRTOvr.st_mtime);
|
|
for (const auto ®ion : regions)
|
|
{
|
|
dfTotalPixels +=
|
|
static_cast<double>(region.nDstXSize) * region.nDstYSize;
|
|
}
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"--partial-refresh-from-source-timestamp only works on a VRT "
|
|
"dataset");
|
|
return false;
|
|
}
|
|
|
|
if (!regions.empty())
|
|
{
|
|
double dfCurPixels = 0;
|
|
for (const auto ®ion : regions)
|
|
{
|
|
if (pfnProgress == GDALDummyProgress)
|
|
{
|
|
CPLDebug("GDAL", "Refresh from source %s",
|
|
region.osFilename.c_str());
|
|
}
|
|
else
|
|
{
|
|
printf("Refresh from source %s.\n", region.osFilename.c_str());
|
|
}
|
|
double dfNextCurPixels =
|
|
dfCurPixels +
|
|
static_cast<double>(region.nDstXSize) * region.nDstYSize;
|
|
void *pScaledProgress = GDALCreateScaledProgress(
|
|
dfCurPixels / dfTotalPixels, dfNextCurPixels / dfTotalPixels,
|
|
pfnProgress, pProgressArg);
|
|
bool bRet = PartialRefresh(
|
|
poDS, anOvrIndices, nBandCount, panBandList, pszResampling,
|
|
region.nDstXOff, region.nDstYOff, region.nDstXSize,
|
|
region.nDstYSize, GDALScaledProgress, pScaledProgress);
|
|
GDALDestroyScaledProgress(pScaledProgress);
|
|
if (!bRet)
|
|
return false;
|
|
dfCurPixels = dfNextCurPixels;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pfnProgress == GDALDummyProgress)
|
|
{
|
|
CPLDebug("GDAL", "No source is more recent than the overviews");
|
|
}
|
|
else
|
|
{
|
|
printf("No source is more recent than the overviews.\n");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PartialRefreshFromSourceExtent() */
|
|
/************************************************************************/
|
|
|
|
static bool PartialRefreshFromSourceExtent(
|
|
GDALDataset *poDS, const CPLStringList &aosSources,
|
|
const char *pszResampling, int nLevelCount, const int *panLevels,
|
|
int nBandCount, const int *panBandList, bool bMinSizeSpecified,
|
|
int nMinSize, GDALProgressFunc pfnProgress, void *pProgressArg)
|
|
{
|
|
std::vector<int> anOvrIndices;
|
|
if (!GetOvrIndices(poDS, nLevelCount, panLevels, bMinSizeSpecified,
|
|
nMinSize, anOvrIndices))
|
|
return false;
|
|
|
|
double adfGeoTransform[6];
|
|
if (poDS->GetGeoTransform(adfGeoTransform) != CE_None)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Dataset has no geotransform");
|
|
return false;
|
|
}
|
|
double adfInvGT[6];
|
|
if (!GDALInvGeoTransform(adfGeoTransform, adfInvGT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
struct Region
|
|
{
|
|
std::string osFileName;
|
|
int nXOff;
|
|
int nYOff;
|
|
int nXSize;
|
|
int nYSize;
|
|
};
|
|
|
|
std::vector<Region> regions;
|
|
|
|
double dfTotalPixels = 0;
|
|
for (int i = 0; i < aosSources.size(); ++i)
|
|
{
|
|
auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
|
|
aosSources[i], GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
|
|
if (!poSrcDS)
|
|
return false;
|
|
|
|
double adfSrcGT[6];
|
|
if (poSrcDS->GetGeoTransform(adfSrcGT) != CE_None)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Source dataset has no geotransform");
|
|
return false;
|
|
}
|
|
|
|
const double dfULX = adfSrcGT[0];
|
|
const double dfULY = adfSrcGT[3];
|
|
const double dfLRX = adfSrcGT[0] +
|
|
poSrcDS->GetRasterXSize() * adfSrcGT[1] +
|
|
poSrcDS->GetRasterYSize() * adfSrcGT[2];
|
|
const double dfLRY = adfSrcGT[3] +
|
|
poSrcDS->GetRasterXSize() * adfSrcGT[4] +
|
|
poSrcDS->GetRasterYSize() * adfSrcGT[5];
|
|
const double dfX1 =
|
|
adfInvGT[0] + adfInvGT[1] * dfULX + adfInvGT[2] * dfULY;
|
|
const double dfY1 =
|
|
adfInvGT[3] + adfInvGT[4] * dfULX + adfInvGT[5] * dfULY;
|
|
const double dfX2 =
|
|
adfInvGT[0] + adfInvGT[1] * dfLRX + adfInvGT[2] * dfLRY;
|
|
const double dfY2 =
|
|
adfInvGT[3] + adfInvGT[4] * dfLRX + adfInvGT[5] * dfLRY;
|
|
constexpr double EPS = 1e-8;
|
|
const int nXOff =
|
|
static_cast<int>(std::max(0.0, std::min(dfX1, dfX2)) + EPS);
|
|
const int nYOff =
|
|
static_cast<int>(std::max(0.0, std::min(dfY1, dfY2)) + EPS);
|
|
const int nXSize =
|
|
static_cast<int>(
|
|
std::ceil(std::min(static_cast<double>(poDS->GetRasterXSize()),
|
|
std::max(dfX1, dfX2)) -
|
|
EPS)) -
|
|
nXOff;
|
|
const int nYSize =
|
|
static_cast<int>(
|
|
std::ceil(std::min(static_cast<double>(poDS->GetRasterYSize()),
|
|
std::max(dfY1, dfY2)) -
|
|
EPS)) -
|
|
nYOff;
|
|
|
|
dfTotalPixels += static_cast<double>(nXSize) * nYSize;
|
|
Region region;
|
|
region.osFileName = aosSources[i];
|
|
region.nXOff = nXOff;
|
|
region.nYOff = nYOff;
|
|
region.nXSize = nXSize;
|
|
region.nYSize = nYSize;
|
|
regions.push_back(std::move(region));
|
|
}
|
|
|
|
double dfCurPixels = 0;
|
|
for (const auto ®ion : regions)
|
|
{
|
|
if (pfnProgress == GDALDummyProgress)
|
|
{
|
|
CPLDebug("GDAL", "Refresh from source %s",
|
|
region.osFileName.c_str());
|
|
}
|
|
else
|
|
{
|
|
printf("Refresh from source %s.\n", region.osFileName.c_str());
|
|
}
|
|
double dfNextCurPixels =
|
|
dfCurPixels + static_cast<double>(region.nXSize) * region.nYSize;
|
|
void *pScaledProgress = GDALCreateScaledProgress(
|
|
dfCurPixels / dfTotalPixels, dfNextCurPixels / dfTotalPixels,
|
|
pfnProgress, pProgressArg);
|
|
bool bRet = PartialRefresh(poDS, anOvrIndices, nBandCount, panBandList,
|
|
pszResampling, region.nXOff, region.nYOff,
|
|
region.nXSize, region.nYSize,
|
|
GDALScaledProgress, pScaledProgress);
|
|
GDALDestroyScaledProgress(pScaledProgress);
|
|
if (!bRet)
|
|
return false;
|
|
dfCurPixels = dfNextCurPixels;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PartialRefreshFromProjWin() */
|
|
/************************************************************************/
|
|
|
|
static bool PartialRefreshFromProjWin(
|
|
GDALDataset *poDS, double dfULX, double dfULY, double dfLRX, double dfLRY,
|
|
const char *pszResampling, int nLevelCount, const int *panLevels,
|
|
int nBandCount, const int *panBandList, bool bMinSizeSpecified,
|
|
int nMinSize, GDALProgressFunc pfnProgress, void *pProgressArg)
|
|
{
|
|
std::vector<int> anOvrIndices;
|
|
if (!GetOvrIndices(poDS, nLevelCount, panLevels, bMinSizeSpecified,
|
|
nMinSize, anOvrIndices))
|
|
return false;
|
|
|
|
double adfGeoTransform[6];
|
|
if (poDS->GetGeoTransform(adfGeoTransform) != CE_None)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Dataset has no geotransform");
|
|
return false;
|
|
}
|
|
double adfInvGT[6];
|
|
if (!GDALInvGeoTransform(adfGeoTransform, adfInvGT))
|
|
{
|
|
return false;
|
|
}
|
|
const double dfX1 = adfInvGT[0] + adfInvGT[1] * dfULX + adfInvGT[2] * dfULY;
|
|
const double dfY1 = adfInvGT[3] + adfInvGT[4] * dfULX + adfInvGT[5] * dfULY;
|
|
const double dfX2 = adfInvGT[0] + adfInvGT[1] * dfLRX + adfInvGT[2] * dfLRY;
|
|
const double dfY2 = adfInvGT[3] + adfInvGT[4] * dfLRX + adfInvGT[5] * dfLRY;
|
|
constexpr double EPS = 1e-8;
|
|
const int nXOff =
|
|
static_cast<int>(std::max(0.0, std::min(dfX1, dfX2)) + EPS);
|
|
const int nYOff =
|
|
static_cast<int>(std::max(0.0, std::min(dfY1, dfY2)) + EPS);
|
|
const int nXSize = static_cast<int>(std::ceil(
|
|
std::min(static_cast<double>(poDS->GetRasterXSize()),
|
|
std::max(dfX1, dfX2)) -
|
|
EPS)) -
|
|
nXOff;
|
|
const int nYSize = static_cast<int>(std::ceil(
|
|
std::min(static_cast<double>(poDS->GetRasterYSize()),
|
|
std::max(dfY1, dfY2)) -
|
|
EPS)) -
|
|
nYOff;
|
|
return PartialRefresh(poDS, anOvrIndices, nBandCount, panBandList,
|
|
pszResampling, nXOff, nYOff, nXSize, nYSize,
|
|
pfnProgress, pProgressArg);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* main() */
|
|
/************************************************************************/
|
|
|
|
MAIN_START(nArgc, papszArgv)
|
|
|
|
{
|
|
EarlySetConfigOptions(nArgc, papszArgv);
|
|
GDALAllRegister();
|
|
|
|
nArgc = GDALGeneralCmdLineProcessor(nArgc, &papszArgv, 0);
|
|
if (nArgc < 1)
|
|
exit(-nArgc);
|
|
CPLStringList aosArgv;
|
|
aosArgv.Assign(papszArgv, /* bAssign = */ true);
|
|
|
|
GDALArgumentParser argParser(aosArgv[0], /* bForBinary=*/true);
|
|
|
|
argParser.add_description(_("Builds or rebuilds overview images."));
|
|
|
|
const char *pszEpilog = _(
|
|
"Useful configuration variables :\n"
|
|
" --config USE_RRD YES : Use Erdas Imagine format (.aux) as overview "
|
|
"format.\n"
|
|
"Below, only for external overviews in GeoTIFF format:\n"
|
|
" --config COMPRESS_OVERVIEW {JPEG,LZW,PACKBITS,DEFLATE} : TIFF "
|
|
"compression\n"
|
|
" --config PHOTOMETRIC_OVERVIEW {RGB,YCBCR,...} : TIFF photometric "
|
|
"interp.\n"
|
|
" --config INTERLEAVE_OVERVIEW {PIXEL|BAND} : TIFF interleaving "
|
|
"method\n"
|
|
" --config BIGTIFF_OVERVIEW {IF_NEEDED|IF_SAFER|YES|NO} : is BigTIFF "
|
|
"used\n"
|
|
"\n"
|
|
"Examples:\n"
|
|
" %% gdaladdo -r average abc.tif\n"
|
|
" %% gdaladdo --config COMPRESS_OVERVIEW JPEG\n"
|
|
" --config PHOTOMETRIC_OVERVIEW YCBCR\n"
|
|
" --config INTERLEAVE_OVERVIEW PIXEL -ro abc.tif\n"
|
|
"\n"
|
|
"For more details, consult https://gdal.org/programs/gdaladdo.html");
|
|
argParser.add_epilog(pszEpilog);
|
|
|
|
std::string osResampling;
|
|
argParser.add_argument("-r")
|
|
.store_into(osResampling)
|
|
.metavar("nearest|average|rms|gauss|bilinear|cubic|cubicspline|lanczos|"
|
|
"average_magphase|mode")
|
|
.help(_("Select a resampling algorithm."));
|
|
|
|
bool bReadOnly = false;
|
|
argParser.add_argument("-ro").store_into(bReadOnly).help(
|
|
_("Open the dataset in read-only mode, in order to generate external "
|
|
"overview."));
|
|
|
|
bool bQuiet = false;
|
|
argParser.add_quiet_argument(&bQuiet);
|
|
|
|
std::vector<int> anBandList;
|
|
argParser.add_argument("-b")
|
|
.append()
|
|
.metavar("<band>")
|
|
.action(
|
|
[&anBandList](const std::string &s)
|
|
{
|
|
const int nBand = atoi(s.c_str());
|
|
if (nBand < 1)
|
|
{
|
|
throw std::invalid_argument(CPLSPrintf(
|
|
"Unrecognizable band number (%s).", s.c_str()));
|
|
}
|
|
anBandList.push_back(nBand);
|
|
})
|
|
.help(_("Select input band(s) for overview generation."));
|
|
|
|
CPLStringList aosOpenOptions;
|
|
argParser.add_argument("-oo")
|
|
.append()
|
|
.metavar("<NAME=VALUE>")
|
|
.action([&aosOpenOptions](const std::string &s)
|
|
{ aosOpenOptions.AddString(s.c_str()); })
|
|
.help(_("Dataset open option (format-specific)."));
|
|
|
|
int nMinSize = 256;
|
|
argParser.add_argument("-minsize")
|
|
.default_value(nMinSize)
|
|
.metavar("<val>")
|
|
.store_into(nMinSize)
|
|
.help(_("Maximum width or height of the smallest overview level."));
|
|
|
|
bool bClean = false;
|
|
bool bPartialRefreshFromSourceTimestamp = false;
|
|
std::string osPartialRefreshFromSourceExtent;
|
|
|
|
{
|
|
auto &group = argParser.add_mutually_exclusive_group();
|
|
group.add_argument("-clean").store_into(bClean).help(
|
|
_("Remove all overviews."));
|
|
|
|
group.add_argument("--partial-refresh-from-source-timestamp")
|
|
.store_into(bPartialRefreshFromSourceTimestamp)
|
|
.help(_("Performs a partial refresh of existing overviews, when "
|
|
"<filename> is a VRT file with an external overview."));
|
|
|
|
group.add_argument("--partial-refresh-from-projwin")
|
|
.metavar("<ulx> <uly> <lrx> <lry>")
|
|
.nargs(4)
|
|
.scan<'g', double>()
|
|
.help(
|
|
_("Performs a partial refresh of existing overviews, in the "
|
|
"region of interest specified by georeference coordinates."));
|
|
|
|
group.add_argument("--partial-refresh-from-source-extent")
|
|
.metavar("<filename1>[,<filenameN>]...")
|
|
.store_into(osPartialRefreshFromSourceExtent)
|
|
.help(
|
|
_("Performs a partial refresh of existing overviews, in the "
|
|
"region of interest specified by one or several filename."));
|
|
}
|
|
|
|
std::string osFilename;
|
|
argParser.add_argument("filename")
|
|
.store_into(osFilename)
|
|
.help(_("The file to build overviews for (or whose overviews must be "
|
|
"removed)."));
|
|
|
|
argParser.add_argument("level").remaining().metavar("<level>").help(
|
|
_("A list of integral overview levels to build."));
|
|
|
|
try
|
|
{
|
|
argParser.parse_args(aosArgv);
|
|
}
|
|
catch (const std::exception &err)
|
|
{
|
|
argParser.display_error_and_usage(err);
|
|
std::exit(1);
|
|
}
|
|
|
|
std::vector<int> anLevels;
|
|
auto levels = argParser.present<std::vector<std::string>>("level");
|
|
if (levels)
|
|
{
|
|
for (const auto &level : *levels)
|
|
{
|
|
anLevels.push_back(atoi(level.c_str()));
|
|
if (anLevels.back() == 1)
|
|
{
|
|
printf(
|
|
"Warning: Overview with subsampling factor of 1 requested. "
|
|
"This will copy the full resolution dataset in the "
|
|
"overview!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
GDALProgressFunc pfnProgress =
|
|
bQuiet ? GDALDummyProgress : GDALTermProgress;
|
|
const bool bMinSizeSpecified = argParser.is_used("-minsize");
|
|
|
|
CPLStringList aosSources;
|
|
if (!osPartialRefreshFromSourceExtent.empty())
|
|
{
|
|
aosSources = CSLTokenizeString2(
|
|
osPartialRefreshFromSourceExtent.c_str(), ",", 0);
|
|
}
|
|
|
|
bool bPartialRefreshFromProjWin = false;
|
|
double dfULX = 0;
|
|
double dfULY = 0;
|
|
double dfLRX = 0;
|
|
double dfLRY = 0;
|
|
if (auto oProjWin = argParser.present<std::vector<double>>(
|
|
"--partial-refresh-from-projwin"))
|
|
{
|
|
bPartialRefreshFromProjWin = true;
|
|
dfULX = (*oProjWin)[0];
|
|
dfULY = (*oProjWin)[1];
|
|
dfLRX = (*oProjWin)[2];
|
|
dfLRY = (*oProjWin)[3];
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Open data file. */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALDatasetH hDataset = nullptr;
|
|
if (!bReadOnly)
|
|
{
|
|
CPLPushErrorHandler(GDALAddoErrorHandler);
|
|
CPLSetCurrentErrorHandlerCatchDebug(FALSE);
|
|
hDataset =
|
|
GDALOpenEx(osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_UPDATE,
|
|
nullptr, aosOpenOptions.List(), nullptr);
|
|
CPLPopErrorHandler();
|
|
if (hDataset != nullptr)
|
|
{
|
|
for (size_t i = 0; i < aoErrors.size(); i++)
|
|
{
|
|
CPLError(aoErrors[i].m_eErr, aoErrors[i].m_errNum, "%s",
|
|
aoErrors[i].m_osMsg.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hDataset == nullptr)
|
|
hDataset = GDALOpenEx(osFilename.c_str(),
|
|
GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, nullptr,
|
|
aosOpenOptions.List(), nullptr);
|
|
if (hDataset == nullptr)
|
|
exit(2);
|
|
|
|
if (!bClean && osResampling.empty())
|
|
{
|
|
auto poDS = GDALDataset::FromHandle(hDataset);
|
|
if (poDS->GetRasterCount() > 0)
|
|
{
|
|
auto poBand = poDS->GetRasterBand(1);
|
|
if (poBand->GetOverviewCount() > 0)
|
|
{
|
|
const char *pszResampling =
|
|
poBand->GetOverview(0)->GetMetadataItem("RESAMPLING");
|
|
if (pszResampling)
|
|
{
|
|
osResampling = pszResampling;
|
|
if (pfnProgress == GDALDummyProgress)
|
|
CPLDebug("GDAL",
|
|
"Reusing resampling method %s from existing "
|
|
"overview",
|
|
pszResampling);
|
|
else
|
|
printf("Info: reusing resampling method %s from "
|
|
"existing overview.\n",
|
|
pszResampling);
|
|
}
|
|
}
|
|
}
|
|
if (osResampling.empty())
|
|
osResampling = "nearest";
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Clean overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
int nResultStatus = 0;
|
|
void *pProgressArg = nullptr;
|
|
const int nBandCount = static_cast<int>(anBandList.size());
|
|
if (bClean)
|
|
{
|
|
if (GDALBuildOverviews(hDataset, "NONE", 0, nullptr, 0, nullptr,
|
|
pfnProgress, pProgressArg) != CE_None)
|
|
{
|
|
fprintf(stderr, "Cleaning overviews failed.\n");
|
|
nResultStatus = 200;
|
|
}
|
|
}
|
|
else if (bPartialRefreshFromSourceTimestamp)
|
|
{
|
|
if (!PartialRefreshFromSourceTimestamp(
|
|
GDALDataset::FromHandle(hDataset), osResampling.c_str(),
|
|
static_cast<int>(anLevels.size()), anLevels.data(), nBandCount,
|
|
anBandList.data(), bMinSizeSpecified, nMinSize, pfnProgress,
|
|
pProgressArg))
|
|
{
|
|
nResultStatus = 1;
|
|
}
|
|
}
|
|
else if (bPartialRefreshFromProjWin)
|
|
{
|
|
if (!PartialRefreshFromProjWin(
|
|
GDALDataset::FromHandle(hDataset), dfULX, dfULY, dfLRX, dfLRY,
|
|
osResampling.c_str(), static_cast<int>(anLevels.size()),
|
|
anLevels.data(), nBandCount, anBandList.data(),
|
|
bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg))
|
|
{
|
|
nResultStatus = 1;
|
|
}
|
|
}
|
|
else if (!aosSources.empty())
|
|
{
|
|
if (!PartialRefreshFromSourceExtent(
|
|
GDALDataset::FromHandle(hDataset), aosSources,
|
|
osResampling.c_str(), static_cast<int>(anLevels.size()),
|
|
anLevels.data(), nBandCount, anBandList.data(),
|
|
bMinSizeSpecified, nMinSize, pfnProgress, pProgressArg))
|
|
{
|
|
nResultStatus = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Generate overviews. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
|
|
// If no levels are specified, reuse the potentially existing ones.
|
|
if (anLevels.empty())
|
|
{
|
|
auto poDS = GDALDataset::FromHandle(hDataset);
|
|
if (poDS->GetRasterCount() > 0)
|
|
{
|
|
auto poBand = poDS->GetRasterBand(1);
|
|
const int nExistingCount = poBand->GetOverviewCount();
|
|
if (nExistingCount > 0)
|
|
{
|
|
for (int iOvr = 0; iOvr < nExistingCount; ++iOvr)
|
|
{
|
|
auto poOverview = poBand->GetOverview(iOvr);
|
|
if (poOverview)
|
|
{
|
|
const int nOvFactor = GDALComputeOvFactor(
|
|
poOverview->GetXSize(), poBand->GetXSize(),
|
|
poOverview->GetYSize(), poBand->GetYSize());
|
|
anLevels.push_back(nOvFactor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (anLevels.empty())
|
|
{
|
|
const int nXSize = GDALGetRasterXSize(hDataset);
|
|
const int nYSize = GDALGetRasterYSize(hDataset);
|
|
int nOvrFactor = 1;
|
|
while (DIV_ROUND_UP(nXSize, nOvrFactor) > nMinSize ||
|
|
DIV_ROUND_UP(nYSize, nOvrFactor) > nMinSize)
|
|
{
|
|
nOvrFactor *= 2;
|
|
anLevels.push_back(nOvrFactor);
|
|
}
|
|
}
|
|
|
|
// Only HFA supports selected layers
|
|
if (nBandCount > 0)
|
|
CPLSetConfigOption("USE_RRD", "YES");
|
|
|
|
if (!anLevels.empty() &&
|
|
GDALBuildOverviews(hDataset, osResampling.c_str(),
|
|
static_cast<int>(anLevels.size()),
|
|
anLevels.data(), nBandCount, anBandList.data(),
|
|
pfnProgress, pProgressArg) != CE_None)
|
|
{
|
|
fprintf(stderr, "Overview building failed.\n");
|
|
nResultStatus = 100;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Cleanup */
|
|
/* -------------------------------------------------------------------- */
|
|
if (GDALClose(hDataset) != CE_None)
|
|
{
|
|
if (nResultStatus == 0)
|
|
nResultStatus = 1;
|
|
}
|
|
|
|
GDALDestroyDriverManager();
|
|
|
|
return nResultStatus;
|
|
}
|
|
|
|
MAIN_END
|