gdal/apps/gdal_create.cpp

531 lines
17 KiB
C++

/******************************************************************************
*
* Project: GDAL Utilities
* Purpose: GDAL Raster creation utility
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2020, 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 "gdal.h"
#include "commonutils.h"
#include "gdalargumentparser.h"
#include "ogr_spatialref.h"
#include <cstdlib>
#include <memory>
#include <vector>
/**
* @brief Makes sure the GDAL library is properly cleaned up before exiting.
* @param nCode exit code
* @todo Move to API
*/
static void GDALExit(int nCode)
{
GDALDestroy();
exit(nCode);
}
/************************************************************************/
/* GDALCreateOptions */
/************************************************************************/
struct GDALCreateOptions
{
int nBandCount = -1;
int nPixels = 0;
bool bPixelsSet{false};
int nLines = 0;
GDALDataType eDT = GDT_Unknown;
double dfULX = 0;
double dfULY = 0;
double dfLRX = 0;
double dfLRY = 0;
int nULCounter{0};
bool bGeoTransform = false;
std::string osOutputSRS;
CPLStringList aosMetadata;
std::vector<double> adfBurnValues;
bool bQuiet = false;
bool bSetNoData = false;
std::string osNoData;
std::string osOutputFilename;
std::string osInputFilename;
std::string osFormat;
CPLStringList aosCreateOptions;
};
/************************************************************************/
/* GDALCreateAppOptionsGetParser() */
/************************************************************************/
static std::unique_ptr<GDALArgumentParser>
GDALCreateAppOptionsGetParser(GDALCreateOptions *psOptions)
{
auto argParser = std::make_unique<GDALArgumentParser>(
"gdal_create", /* bForBinary */ true);
argParser->add_description(
_("Create a raster file (without source dataset)."));
argParser->add_epilog(_(
"For more details, consult the full documentation for the gdal_create "
"utility: http://gdal.org/gdal_create.html"));
argParser->add_output_type_argument(psOptions->eDT);
argParser->add_output_format_argument(psOptions->osFormat);
argParser->add_argument("-outsize")
.metavar("<xsize> <ysize>")
.nargs(2)
.scan<'i', int>()
.action(
[psOptions](const std::string &s)
{
if (!psOptions->bPixelsSet)
{
psOptions->nPixels = atoi(s.c_str());
psOptions->bPixelsSet = true;
}
else
{
psOptions->nLines = atoi(s.c_str());
}
})
.help(_("Set the size of the output file."));
argParser->add_argument("-bands")
.metavar("<count>")
.store_into(psOptions->nBandCount)
.help(_("Set the number of bands in the output file."));
argParser->add_argument("-burn").metavar("<value>").append().help(
_("A fixed value to burn into a band. A list of "
"-burn options can be supplied, one per band being written to."));
argParser->add_argument("-a_srs")
.metavar("<srs_def>")
.store_into(psOptions->osOutputSRS)
.help(_("Override the projection for the output file. "));
argParser->add_argument("-a_ullr")
.metavar("<ulx> <uly> <lrx> <lry>")
.scan<'g', double>()
.nargs(4)
.action(
[psOptions](const std::string &s)
{
switch (psOptions->nULCounter++)
{
case 0:
psOptions->bGeoTransform = true;
psOptions->dfULX = CPLAtofM(s.c_str());
break;
case 1:
psOptions->dfULY = CPLAtofM(s.c_str());
break;
case 2:
psOptions->dfLRX = CPLAtofM(s.c_str());
break;
case 3:
psOptions->dfLRY = CPLAtof(s.c_str());
break;
}
})
.help(_("Assign the georeferenced bounds of the output file. "));
argParser->add_argument("-a_nodata")
.metavar("<value>")
.scan<'g', double>()
.action(
[psOptions](const std::string &s)
{
psOptions->bSetNoData = true;
psOptions->osNoData = s;
})
.help(_("Assign a specified nodata value to output bands."));
argParser->add_metadata_item_options_argument(psOptions->aosMetadata);
argParser->add_creation_options_argument(psOptions->aosCreateOptions);
argParser->add_quiet_argument(&psOptions->bQuiet);
argParser->add_argument("-if")
.metavar("<input_dataset>")
.store_into(psOptions->osInputFilename)
.help(_("Name of GDAL input dataset that serves as a template for "
"default values of options -outsize, -bands, -ot, -a_srs, "
"-a_ullr and -a_nodata."));
argParser->add_argument("out_dataset")
.metavar("<out_dataset>")
.store_into(psOptions->osOutputFilename)
.help(_("Name of the output dataset to create."));
return argParser;
}
/************************************************************************/
/* main() */
/************************************************************************/
MAIN_START(argc, argv)
{
/* Check strict compilation and runtime library version as we use C++ API */
if (!GDAL_CHECK_VERSION(argv[0]))
exit(1);
EarlySetConfigOptions(argc, argv);
/* -------------------------------------------------------------------- */
/* Register standard GDAL drivers, and process generic GDAL */
/* command options. */
/* -------------------------------------------------------------------- */
GDALAllRegister();
argc = GDALGeneralCmdLineProcessor(argc, &argv, 0);
if (argc < 1)
GDALExit(-argc);
if (argc < 2)
{
try
{
GDALCreateOptions sOptions;
auto argParser = GDALCreateAppOptionsGetParser(&sOptions);
fprintf(stderr, "%s\n", argParser->usage().c_str());
}
catch (const std::exception &err)
{
CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
err.what());
}
CSLDestroy(argv);
GDALExit(1);
}
GDALCreateOptions sOptions;
CPLStringList aosArgv;
for (int iArg = 1; iArg < argc; iArg++)
{
if (iArg + 1 < argc && EQUAL(argv[iArg], "-burn"))
{
++iArg;
while (true)
{
if (strchr(argv[iArg], ' '))
{
const CPLStringList aosTokens(
CSLTokenizeString(argv[iArg]));
for (int i = 0; i < aosTokens.size(); i++)
{
char *endptr = nullptr;
sOptions.adfBurnValues.push_back(
CPLStrtodM(aosTokens[i], &endptr));
if (endptr != aosTokens[i] + strlen(aosTokens[i]))
{
fprintf(stderr, "Invalid value for -burn\n");
CSLDestroy(argv);
GDALExit(1);
}
}
}
else
{
char *endptr = nullptr;
sOptions.adfBurnValues.push_back(
CPLStrtodM(argv[iArg], &endptr));
if (endptr != argv[iArg] + strlen(argv[iArg]))
{
fprintf(stderr, "Invalid value for -burn\n");
CSLDestroy(argv);
GDALExit(1);
}
}
if (iArg + 1 < argc &&
CPLGetValueType(argv[iArg + 1]) != CPL_VALUE_STRING)
{
++iArg;
}
else
{
break;
}
}
}
else
{
aosArgv.AddString(argv[iArg]);
}
}
CSLDestroy(argv);
try
{
auto argParser = GDALCreateAppOptionsGetParser(&sOptions);
argParser->parse_args_without_binary_name(aosArgv.List());
}
catch (const std::exception &error)
{
CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
GDALExit(1);
}
double adfGeoTransform[6] = {0, 1, 0, 0, 0, 1};
if (sOptions.bGeoTransform && sOptions.nPixels > 0 && sOptions.nLines > 0)
{
adfGeoTransform[0] = sOptions.dfULX;
adfGeoTransform[1] =
(sOptions.dfLRX - sOptions.dfULX) / sOptions.nPixels;
adfGeoTransform[2] = 0;
adfGeoTransform[3] = sOptions.dfULY;
adfGeoTransform[4] = 0;
adfGeoTransform[5] =
(sOptions.dfLRY - sOptions.dfULY) / sOptions.nLines;
}
std::unique_ptr<GDALDataset> poInputDS;
if (!sOptions.osInputFilename.empty())
{
poInputDS.reset(
GDALDataset::Open(sOptions.osInputFilename.c_str(),
GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
if (poInputDS == nullptr)
{
GDALExit(1);
}
if (sOptions.nPixels == 0)
{
sOptions.nPixels = poInputDS->GetRasterXSize();
sOptions.nLines = poInputDS->GetRasterYSize();
}
if (sOptions.nBandCount < 0)
{
sOptions.nBandCount = poInputDS->GetRasterCount();
}
if (sOptions.eDT == GDT_Unknown && poInputDS->GetRasterCount() > 0)
{
sOptions.eDT = poInputDS->GetRasterBand(1)->GetRasterDataType();
}
if (sOptions.osOutputSRS.empty())
{
sOptions.osOutputSRS = poInputDS->GetProjectionRef();
}
if (!(sOptions.bGeoTransform && sOptions.nPixels > 0 &&
sOptions.nLines > 0))
{
if (poInputDS->GetGeoTransform(adfGeoTransform) == CE_None)
{
sOptions.bGeoTransform = true;
}
}
if (!sOptions.bSetNoData && poInputDS->GetRasterCount() > 0)
{
if (sOptions.eDT == GDT_Int64)
{
int noData;
const auto nNoDataValue =
poInputDS->GetRasterBand(1)->GetNoDataValueAsInt64(&noData);
sOptions.bSetNoData = noData;
if (sOptions.bSetNoData)
sOptions.osNoData = CPLSPrintf(
CPL_FRMT_GIB, static_cast<GIntBig>(nNoDataValue));
}
else if (sOptions.eDT == GDT_UInt64)
{
int noData;
const auto nNoDataValue =
poInputDS->GetRasterBand(1)->GetNoDataValueAsUInt64(
&noData);
sOptions.bSetNoData = noData;
if (sOptions.bSetNoData)
sOptions.osNoData = CPLSPrintf(
CPL_FRMT_GUIB, static_cast<GUIntBig>(nNoDataValue));
}
else
{
int noData;
const double dfNoDataValue =
poInputDS->GetRasterBand(1)->GetNoDataValue(&noData);
sOptions.bSetNoData = noData;
if (sOptions.bSetNoData)
sOptions.osNoData = CPLSPrintf("%.18g", dfNoDataValue);
}
}
}
GDALDriverH hDriver = GDALGetDriverByName(
sOptions.osFormat.empty()
? GetOutputDriverForRaster(sOptions.osOutputFilename.c_str())
.c_str()
: sOptions.osFormat.c_str());
if (hDriver == nullptr)
{
fprintf(stderr, "Output driver not found.\n");
GDALExit(1);
}
const bool bHasCreate =
GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) != nullptr;
if (!bHasCreate &&
GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) == nullptr)
{
fprintf(stderr, "This driver has no creation capabilities.\n");
GDALExit(1);
}
GDALDriverH hTmpDriver = GDALGetDriverByName("MEM");
if (!bHasCreate && hTmpDriver == nullptr)
{
fprintf(stderr, "MEM driver not available.\n");
GDALExit(1);
}
if (sOptions.nPixels != 0 && sOptions.eDT == GDT_Unknown)
{
sOptions.eDT = GDT_Byte;
}
if (sOptions.nBandCount < 0)
{
sOptions.nBandCount = sOptions.eDT == GDT_Unknown ? 0 : 1;
}
GDALDatasetH hDS = GDALCreate(
bHasCreate ? hDriver : hTmpDriver, sOptions.osOutputFilename.c_str(),
sOptions.nPixels, sOptions.nLines, sOptions.nBandCount, sOptions.eDT,
bHasCreate ? sOptions.aosCreateOptions.List() : nullptr);
if (hDS == nullptr)
{
GDALExit(1);
}
if (!sOptions.osOutputSRS.empty() &&
!EQUAL(sOptions.osOutputSRS.c_str(), "NONE"))
{
OGRSpatialReference oSRS;
oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
if (oSRS.SetFromUserInput(sOptions.osOutputSRS.c_str()) != OGRERR_NONE)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Failed to process SRS definition: %s",
sOptions.osOutputSRS.c_str());
GDALExit(1);
}
char *pszSRS = nullptr;
oSRS.exportToWkt(&pszSRS);
if (GDALSetProjection(hDS, pszSRS) != CE_None)
{
CPLFree(pszSRS);
GDALClose(hDS);
GDALExit(1);
}
CPLFree(pszSRS);
}
if (sOptions.bGeoTransform)
{
if (sOptions.nPixels == 0)
{
fprintf(stderr,
"-outsize must be specified when -a_ullr is used.\n");
GDALClose(hDS);
GDALExit(1);
}
if (GDALSetGeoTransform(hDS, adfGeoTransform) != CE_None)
{
GDALClose(hDS);
GDALExit(1);
}
}
else if (poInputDS && poInputDS->GetGCPCount() > 0)
{
GDALDataset::FromHandle(hDS)->SetGCPs(poInputDS->GetGCPCount(),
poInputDS->GetGCPs(),
poInputDS->GetGCPSpatialRef());
}
if (!sOptions.aosMetadata.empty())
{
GDALSetMetadata(hDS, sOptions.aosMetadata.List(), nullptr);
}
const int nBands = GDALGetRasterCount(hDS);
if (sOptions.bSetNoData)
{
for (int i = 0; i < nBands; i++)
{
auto hBand = GDALGetRasterBand(hDS, i + 1);
if (sOptions.eDT == GDT_Int64)
{
GDALSetRasterNoDataValueAsInt64(
hBand, static_cast<int64_t>(std::strtoll(
sOptions.osNoData.c_str(), nullptr, 10)));
}
else if (sOptions.eDT == GDT_UInt64)
{
GDALSetRasterNoDataValueAsUInt64(
hBand, static_cast<uint64_t>(std::strtoull(
sOptions.osNoData.c_str(), nullptr, 10)));
}
else
{
GDALSetRasterNoDataValue(hBand,
CPLAtofM(sOptions.osNoData.c_str()));
}
}
}
if (!sOptions.adfBurnValues.empty())
{
for (int i = 0; i < nBands; i++)
{
GDALFillRaster(GDALGetRasterBand(hDS, i + 1),
i < static_cast<int>(sOptions.adfBurnValues.size())
? sOptions.adfBurnValues[i]
: sOptions.adfBurnValues.back(),
0);
}
}
bool bHasGotErr = false;
if (!bHasCreate)
{
GDALDatasetH hOutDS = GDALCreateCopy(
hDriver, sOptions.osOutputFilename.c_str(), hDS, false,
sOptions.aosCreateOptions.List(),
sOptions.bQuiet ? GDALDummyProgress : GDALTermProgress, nullptr);
if (hOutDS == nullptr)
{
GDALClose(hDS);
GDALExit(1);
}
if (GDALClose(hOutDS) != CE_None)
{
bHasGotErr = true;
}
}
const bool bWasFailureBefore = (CPLGetLastErrorType() == CE_Failure);
if (GDALClose(hDS) != CE_None)
bHasGotErr = true;
if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
{
bHasGotErr = true;
}
return bHasGotErr ? 1 : 0;
}
MAIN_END