Implement RFC 104: Adding a "gdal" front-end command line interface

This commit is contained in:
Even Rouault 2024-11-14 16:38:55 +01:00
parent 7ab4e4fa46
commit ef8d0f6baa
No known key found for this signature in database
GPG Key ID: 33EBBFC47B3DD87D
55 changed files with 14058 additions and 8 deletions

View File

@ -6,6 +6,21 @@ add_library(
commonutils.h
gdal_utils.h
gdalargumentparser.cpp
gdalalg_convert.cpp
gdalalg_info.cpp
gdalalg_main.cpp
gdalalg_pipeline.cpp
gdalalg_raster.cpp
gdalalg_raster_info.cpp
gdalalg_raster_convert.cpp
gdalalg_vector.cpp
gdalalg_vector_info.cpp
gdalalg_vector_convert.cpp
gdalalg_vector_pipeline.cpp
gdalalg_vector_read.cpp
gdalalg_vector_filter.cpp
gdalalg_vector_reproject.cpp
gdalalg_vector_write.cpp
gdalinfo_lib.cpp
gdalbuildvrt_lib.cpp
gdal_grid_lib.cpp
@ -49,6 +64,7 @@ target_public_header(TARGET ${GDAL_LIB_TARGET_NAME} HEADERS gdal_utils.h)
if (BUILD_APPS)
# Default Apps
add_executable(gdal gdal_utils_priv.h gdal.cpp)
add_executable(gdalinfo gdal_utils_priv.h gdalinfo_bin.cpp)
add_executable(gdalbuildvrt gdal_utils_priv.h gdalbuildvrt_bin.cpp)
add_executable(gdaladdo gdal_utils_priv.h gdaladdo.cpp)
@ -89,6 +105,7 @@ if (BUILD_APPS)
add_dependencies(utils_common generate_gdal_version_h)
set(APPS_TARGETS
gdal
gdalinfo
gdalbuildvrt
gdaladdo

85
apps/gdal.cpp Normal file
View File

@ -0,0 +1,85 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: CLI front-end
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalgorithm.h"
#include "commonutils.h"
#include "gdal.h"
#include <cassert>
/************************************************************************/
/* main() */
/************************************************************************/
MAIN_START(argc, argv)
{
EarlySetConfigOptions(argc, argv);
/* -------------------------------------------------------------------- */
/* Register standard GDAL drivers, and process generic GDAL */
/* command options. */
/* -------------------------------------------------------------------- */
GDALAllRegister();
argc = GDALGeneralCmdLineProcessor(
argc, &argv, GDAL_OF_RASTER | GDAL_OF_VECTOR | GDAL_OF_MULTIDIM_RASTER);
if (argc < 1)
return (-argc);
auto alg = GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME);
assert(alg);
alg->SetCallPath(std::vector<std::string>{argv[0]});
std::vector<std::string> args;
for (int i = 1; i < argc; ++i)
args.push_back(argv[i]);
if (!alg->ParseCommandLineArguments(args))
{
fprintf(stderr, "%s", alg->GetUsageForCLI(true).c_str());
CSLDestroy(argv);
return 1;
}
{
const auto stdoutArg = alg->GetActualAlgorithm().GetArg("stdout");
if (stdoutArg && stdoutArg->GetType() == GAAT_BOOLEAN)
stdoutArg->Set(true);
}
GDALProgressFunc pfnProgress =
alg->IsProgressBarRequested() ? GDALTermProgress : nullptr;
void *pProgressData = nullptr;
int ret = 0;
if (alg->Run(pfnProgress, pProgressData) && alg->Finalize())
{
const auto outputArg =
alg->GetActualAlgorithm().GetArg("output-string");
if (outputArg && outputArg->GetType() == GAAT_STRING &&
outputArg->IsOutput())
{
printf("%s", outputArg->Get<std::string>().c_str());
}
}
else
{
ret = 1;
}
CSLDestroy(argv);
return ret;
}
MAIN_END

View File

@ -301,6 +301,9 @@ struct GDALTranslateOptions
/*! overview level of source file to be used */
int nOvLevel = OVR_LEVEL_AUTO;
/*! set to true to prevent overwriting existing dataset */
bool bNoOverwrite = false;
GDALTranslateOptions() = default;
~GDALTranslateOptions();
GDALTranslateOptions *Clone() const;
@ -1106,6 +1109,29 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset,
"@QUIET_DELETE_ON_CREATE_COPY", "NO");
}
if (psOptions->bNoOverwrite && !EQUAL(pszDest, ""))
{
VSIStatBufL sStat;
if (VSIStatL(pszDest, &sStat) == 0)
{
CPLError(CE_Failure, CPLE_AppDefined,
"File '%s' already exists. Specify the --overwrite "
"option to overwrite it.",
pszDest);
GDALTranslateOptionsFree(psOptions);
return nullptr;
}
else if (std::unique_ptr<GDALDataset>(GDALDataset::Open(pszDest)))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Dataset '%s' already exists. Specify the --overwrite "
"option to overwrite it.",
pszDest);
GDALTranslateOptionsFree(psOptions);
return nullptr;
}
}
GDALDriver::FromHandle(hDriver)->QuietDeleteForCreateCopy(pszDest,
poSrcDS);
@ -3127,6 +3153,11 @@ GDALTranslateOptionsGetParser(GDALTranslateOptions *psOptions,
.hidden()
.store_into(psOptions->nLimitOutSize);
// Undocumented option used by gdal raster convert
argParser->add_argument("--no-overwrite")
.store_into(psOptions->bNoOverwrite)
.hidden();
if (psOptionsForBinary)
{
argParser->add_argument("input_file")

59
apps/gdalalg_convert.cpp Normal file
View File

@ -0,0 +1,59 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "convert" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "cpl_error.h"
#include "gdalalgorithm.h"
#include "gdalalg_raster_convert.h"
#include "gdalalg_vector_convert.h"
#include "gdalalg_dispatcher.h"
#include "gdal_priv.h"
/************************************************************************/
/* GDALConvertAlgorithm */
/************************************************************************/
class GDALConvertAlgorithm
: public GDALDispatcherAlgorithm<GDALRasterConvertAlgorithm,
GDALVectorConvertAlgorithm>
{
public:
static constexpr const char *NAME = "convert";
static constexpr const char *DESCRIPTION =
"Convert a dataset (shortcut for 'gdal raster convert' or "
"'gdal vector convert').";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALConvertAlgorithm()
: GDALDispatcherAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
// only for the help message
AddProgressArg();
AddOutputFormatArg(&m_format);
AddInputDatasetArg(&m_inputDataset);
AddOutputDatasetArg(&m_outputDataset);
m_longDescription = "For all options, run 'gdal raster convert --help' "
"or 'gdal vector convert --help'";
}
private:
std::string m_format{};
GDALArgDatasetValue m_inputDataset{};
GDALArgDatasetValue m_outputDataset{};
};
GDAL_STATIC_REGISTER_ALG(GDALConvertAlgorithm);

238
apps/gdalalg_dispatcher.h Normal file
View File

@ -0,0 +1,238 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal subcommand dispatcher
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_DISPATCHER_INCLUDED
#define GDALALG_DISPATCHER_INCLUDED
#include "gdalalgorithm.h"
#include "gdal_priv.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALDispatcherAlgorithm */
/************************************************************************/
template <class RasterDispatcher, class VectorDispatcher>
class GDALDispatcherAlgorithm : public GDALAlgorithm
{
public:
GDALDispatcherAlgorithm(const std::string &name,
const std::string &description,
const std::string &helpURL)
: GDALAlgorithm(name, description, helpURL),
m_rasterDispatcher(std::make_unique<RasterDispatcher>(
/* openForMixedRasterVector = */ true)),
m_vectorDispatcher(std::make_unique<VectorDispatcher>())
{
// A "info" dispacher command is a shortcut for something like
// "raster info", "vector info". Best to expose the latter.
SetDisplayInJSONUsage(false);
}
bool
ParseCommandLineArguments(const std::vector<std::string> &args) override;
std::string GetUsageForCLI(bool shortUsage,
const UsageOptions &usageOptions) const override;
private:
std::unique_ptr<RasterDispatcher> m_rasterDispatcher{};
std::unique_ptr<VectorDispatcher> m_vectorDispatcher{};
bool m_showUsage = true;
bool RunImpl(GDALProgressFunc, void *) override
{
CPLError(CE_Failure, CPLE_AppDefined,
"The Run() method should not be called directly on the \"gdal "
"%s\" program.",
GetName().c_str());
return false;
}
};
/************************************************************************/
/* GDALDispatcherAlgorithm::ParseCommandLineArguments() */
/************************************************************************/
template <class RasterDispatcher, class VectorDispatcher>
bool GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>::
ParseCommandLineArguments(const std::vector<std::string> &args)
{
bool ok;
{
std::unique_ptr<CPLErrorStateBackuper> oErrorHandler;
if (args.size() > 1)
{
oErrorHandler =
std::make_unique<CPLErrorStateBackuper>(CPLQuietErrorHandler);
}
ok = m_rasterDispatcher->ParseCommandLineArguments(args);
CPL_IGNORE_RET_VAL(oErrorHandler);
}
if (m_rasterDispatcher->PropagateSpecialActionTo(this))
{
return true;
}
if (ok)
{
auto poDS = m_rasterDispatcher->GetDataset();
// cppcheck-suppress knownConditionTrueFalse
if (poDS &&
(poDS->GetRasterCount() > 0 || poDS->GetMetadata("SUBDATASETS")))
{
if (poDS->GetLayerCount() != 0)
{
m_showUsage = false;
CPLError(CE_Failure, CPLE_AppDefined,
"'%s' has both raster and vector content. "
"Please use 'gdal raster %s' or 'gdal vector %s'.",
poDS->GetDescription(), GetName().c_str(),
GetName().c_str());
return false;
}
m_selectedSubAlg = m_rasterDispatcher.get();
std::vector<std::string> callPath(m_callPath);
callPath.push_back("raster");
m_selectedSubAlg->SetCallPath(callPath);
return true;
}
}
else if (args.size() <= 1)
{
return false;
}
auto poDSFromRaster = m_rasterDispatcher->GetDataset();
// cppcheck-suppress knownConditionTrueFalse
if (poDSFromRaster)
{
m_vectorDispatcher->SetDataset(poDSFromRaster, false);
}
std::vector<std::string> argsWithoutInput;
bool skipNext = false;
for (const auto &arg : args)
{
if (arg == "-i" || arg == "--input")
{
skipNext = true;
}
else if (!skipNext)
{
if (!STARTS_WITH(arg.c_str(), "--input=") &&
!(poDSFromRaster && arg == poDSFromRaster->GetDescription()))
{
argsWithoutInput.push_back(arg);
}
}
else
{
skipNext = false;
}
}
{
CPLErrorStateBackuper oErrorHandler(CPLQuietErrorHandler);
ok = m_vectorDispatcher->ParseCommandLineArguments(argsWithoutInput);
}
if (ok)
{
m_selectedSubAlg = m_vectorDispatcher.get();
std::vector<std::string> callPath(m_callPath);
callPath.push_back("vector");
m_selectedSubAlg->SetCallPath(callPath);
return true;
}
bool ret = false;
for (const auto &arg : args)
{
VSIStatBufL sStat;
if (VSIStatL(arg.c_str(), &sStat) == 0)
{
auto poDS =
std::unique_ptr<GDALDataset>(GDALDataset::Open(arg.c_str()));
if (poDS)
{
if (poDS->GetRasterCount() > 0 ||
poDS->GetMetadata("SUBDATASETS"))
{
if (poDS->GetLayerCount() != 0)
{
m_showUsage = false;
CPLError(CE_Failure, CPLE_AppDefined,
"'%s' has both raster and vector content. "
"Please use 'gdal raster %s' or 'gdal "
"vector %s'.",
poDS->GetDescription(), GetName().c_str(),
GetName().c_str());
return false;
}
m_rasterDispatcher = std::make_unique<RasterDispatcher>();
m_rasterDispatcher->SetDataset(poDS.release(), true);
m_selectedSubAlg = m_rasterDispatcher.get();
std::vector<std::string> callPath(m_callPath);
callPath.push_back("raster");
m_selectedSubAlg->SetCallPath(callPath);
ret = m_selectedSubAlg->ParseCommandLineArguments(
argsWithoutInput);
break;
}
else if (poDS->GetLayerCount() != 0)
{
m_vectorDispatcher = std::make_unique<VectorDispatcher>();
m_vectorDispatcher->SetDataset(poDS.release(), true);
m_selectedSubAlg = m_vectorDispatcher.get();
std::vector<std::string> callPath(m_callPath);
callPath.push_back("vector");
m_selectedSubAlg->SetCallPath(callPath);
ret = m_selectedSubAlg->ParseCommandLineArguments(
argsWithoutInput);
break;
}
}
}
}
return ret;
}
/************************************************************************/
/* GDALDispatcherAlgorithm::GetUsageForCLI() */
/************************************************************************/
template <class RasterDispatcher, class VectorDispatcher>
std::string
GDALDispatcherAlgorithm<RasterDispatcher, VectorDispatcher>::GetUsageForCLI(
bool shortUsage, const UsageOptions &usageOptions) const
{
if (m_selectedSubAlg)
{
return m_selectedSubAlg->GetUsageForCLI(shortUsage, usageOptions);
}
if (m_showUsage)
{
return GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
}
return std::string();
}
//! @endcond
#endif // GDALALG_DISPATCHER_INCLUDED

67
apps/gdalalg_info.cpp Normal file
View File

@ -0,0 +1,67 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "info" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "cpl_error.h"
#include "gdalalgorithm.h"
#include "gdalalg_raster_info.h"
#include "gdalalg_vector_info.h"
#include "gdalalg_dispatcher.h"
#include "gdal_priv.h"
/************************************************************************/
/* GDALInfoAlgorithm */
/************************************************************************/
class GDALInfoAlgorithm final
: public GDALDispatcherAlgorithm<GDALRasterInfoAlgorithm,
GDALVectorInfoAlgorithm>
{
public:
static constexpr const char *NAME = "info";
static constexpr const char *DESCRIPTION =
"Return information on a dataset (shortcut for 'gdal raster info' or "
"'gdal vector info').";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALInfoAlgorithm() : GDALDispatcherAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
// only for the help message
AddOutputFormatArg(&m_format).SetDefault("json").SetChoices("json",
"text");
AddInputDatasetArg(&m_dataset);
m_longDescription = "For all options, run 'gdal raster info --help' or "
"'gdal vector info --help'";
}
private:
std::unique_ptr<GDALRasterInfoAlgorithm> m_rasterInfo{};
std::unique_ptr<GDALVectorInfoAlgorithm> m_vectorInfo{};
std::string m_format{};
GDALArgDatasetValue m_dataset{};
bool RunImpl(GDALProgressFunc, void *) override
{
CPLError(CE_Failure, CPLE_AppDefined,
"The Run() method should not be called directly on the \"gdal "
"info\" program.");
return false;
}
};
GDAL_STATIC_REGISTER_ALG(GDALInfoAlgorithm);

123
apps/gdalalg_main.cpp Normal file
View File

@ -0,0 +1,123 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "main" command
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_main.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALMainAlgorithm::GDALMainAlgorithm() */
/************************************************************************/
GDALMainAlgorithm::GDALMainAlgorithm()
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
for (const std::string &subAlgName :
GDALGlobalAlgorithmRegistry::GetSingleton().GetNames())
{
const auto pInfo =
GDALGlobalAlgorithmRegistry::GetSingleton().GetInfo(subAlgName);
if (pInfo)
RegisterSubAlgorithm(*pInfo);
}
m_longDescription = "'gdal <FILENAME>' can also be used as a shortcut for "
"'gdal info <FILENAME>'.\n"
"And 'gdal read <FILENAME> ! ...' as a shortcut for "
"'gdal pipeline <FILENAME> ! ...'.";
SetDisplayInJSONUsage(false);
}
/************************************************************************/
/* GDALMainAlgorithm::ParseCommandLineArguments() */
/************************************************************************/
bool GDALMainAlgorithm::ParseCommandLineArguments(
const std::vector<std::string> &args)
{
if (args.size() >= 2 && args[0] == "read")
{
m_subAlg =
GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate("pipeline");
if (m_subAlg)
{
bool ret = m_subAlg->ParseCommandLineArguments(args);
if (ret)
{
m_selectedSubAlg = &(m_subAlg->GetActualAlgorithm());
std::vector<std::string> callPath(m_callPath);
callPath.push_back("vector");
m_selectedSubAlg->SetCallPath(callPath);
return true;
}
else if (strstr(CPLGetLastErrorMsg(),
"has both raster and vector content"))
{
m_showUsage = false;
return false;
}
}
}
else if (!(args.size() >= 1 && InstantiateSubAlgorithm(args[0])))
{
VSIStatBufL sStat;
for (const auto &arg : args)
{
if (VSIStatL(arg.c_str(), &sStat) == 0)
{
m_subAlg =
GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
"info");
if (m_subAlg)
{
bool ret = m_subAlg->ParseCommandLineArguments(args);
if (ret)
{
m_selectedSubAlg = &(m_subAlg->GetActualAlgorithm());
std::vector<std::string> callPath(m_callPath);
callPath.push_back(m_selectedSubAlg->GetArg("layer")
? "vector"
: "raster");
m_selectedSubAlg->SetCallPath(callPath);
return true;
}
else if (strstr(CPLGetLastErrorMsg(),
"has both raster and vector content"))
{
m_showUsage = false;
return false;
}
}
}
}
}
return GDALAlgorithm::ParseCommandLineArguments(args);
}
/************************************************************************/
/* GDALMainAlgorithm::GetUsageForCLI() */
/************************************************************************/
std::string
GDALMainAlgorithm::GetUsageForCLI(bool shortUsage,
const UsageOptions &usageOptions) const
{
if (m_selectedSubAlg)
return m_selectedSubAlg->GetUsageForCLI(shortUsage, usageOptions);
if (m_showUsage)
return GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
return std::string();
}
//! @endcond

57
apps/gdalalg_main.h Normal file
View File

@ -0,0 +1,57 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "main" command
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_MAIN_INCLUDED
#define GDALALG_MAIN_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALMainAlgorithm */
/************************************************************************/
class GDALMainAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME =
GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME;
static constexpr const char *DESCRIPTION = "Main gdal entry point.";
static constexpr const char *HELP_URL = "/programs/index.html";
static std::vector<std::string> GetAliases()
{
return {};
}
GDALMainAlgorithm();
bool
ParseCommandLineArguments(const std::vector<std::string> &args) override;
std::string GetUsageForCLI(bool shortUsage,
const UsageOptions &usageOptions) const override;
private:
std::unique_ptr<GDALAlgorithm> m_subAlg{};
bool m_showUsage = true;
bool RunImpl(GDALProgressFunc, void *) override
{
return true;
}
};
//! @endcond
#endif // GDALALG_MAIN_INCLUDED

112
apps/gdalalg_pipeline.cpp Normal file
View File

@ -0,0 +1,112 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "pipeline" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "cpl_error.h"
#include "gdalalgorithm.h"
//#include "gdalalg_raster_pipeline.h"
#include "gdalalg_vector_pipeline.h"
#include "gdalalg_dispatcher.h"
#include "gdal_priv.h"
/************************************************************************/
/* GDALDummyRasterPipelineAlgorithm */
/************************************************************************/
class GDALDummyRasterPipelineAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "pipeline";
static constexpr const char *DESCRIPTION = "Dummy raster pipeline.";
static constexpr const char *HELP_URL = "";
static std::vector<std::string> GetAliases()
{
return {};
}
explicit GDALDummyRasterPipelineAlgorithm(bool = false)
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
}
bool ParseCommandLineArguments(const std::vector<std::string> &) override
{
return false;
}
/* cppcheck-suppress functionStatic */
GDALDataset *GetDataset()
{
return nullptr;
}
/* cppcheck-suppress functionStatic */
void SetDataset(GDALDataset *, bool)
{
}
private:
bool RunImpl(GDALProgressFunc, void *) override
{
return false;
}
};
/************************************************************************/
/* GDALPipelineAlgorithm */
/************************************************************************/
class GDALPipelineAlgorithm final
: public GDALDispatcherAlgorithm<GDALDummyRasterPipelineAlgorithm,
GDALVectorPipelineAlgorithm>
{
public:
static constexpr const char *NAME = "pipeline";
static constexpr const char *DESCRIPTION =
"Execute a pipeline (shortcut for 'gdal vector pipeline').";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALPipelineAlgorithm()
: GDALDispatcherAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
// only for the help message
AddOutputFormatArg(&m_format).SetDefault("json").SetChoices("json",
"text");
AddInputDatasetArg(&m_dataset);
m_longDescription =
"For all options, run 'gdal raster pipeline --help' or "
"'gdal vector pipeline --help'";
}
private:
std::unique_ptr<GDALDummyRasterPipelineAlgorithm> m_rasterPipeline{};
std::unique_ptr<GDALVectorPipelineAlgorithm> m_vectorPipeline{};
std::string m_format{};
GDALArgDatasetValue m_dataset{};
bool RunImpl(GDALProgressFunc, void *) override
{
CPLError(CE_Failure, CPLE_AppDefined,
"The Run() method should not be called directly on the \"gdal "
"pipeline\" program.");
return false;
}
};
GDAL_STATIC_REGISTER_ALG(GDALPipelineAlgorithm);

50
apps/gdalalg_raster.cpp Normal file
View File

@ -0,0 +1,50 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalgorithm.h"
#include "gdalalg_raster_info.h"
#include "gdalalg_raster_convert.h"
/************************************************************************/
/* GDALRasterAlgorithm */
/************************************************************************/
class GDALRasterAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "raster";
static constexpr const char *DESCRIPTION = "Raster commands.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALRasterAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
RegisterSubAlgorithm<GDALRasterInfoAlgorithm>();
RegisterSubAlgorithm<GDALRasterConvertAlgorithm>();
}
private:
bool RunImpl(GDALProgressFunc, void *) override
{
CPLError(CE_Failure, CPLE_AppDefined,
"The Run() method should not be called directly on the \"gdal "
"raster\" program.");
return false;
}
};
GDAL_STATIC_REGISTER_ALG(GDALRasterAlgorithm);

View File

@ -0,0 +1,108 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster convert" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_raster_convert.h"
#include "cpl_conv.h"
#include "gdal_priv.h"
#include "gdal_utils.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALRasterConvertAlgorithm::GDALRasterConvertAlgorithm() */
/************************************************************************/
GDALRasterConvertAlgorithm::GDALRasterConvertAlgorithm(
bool openForMixedRasterVector)
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddProgressArg();
AddOutputFormatArg(&m_outputFormat)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
{GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY});
AddOpenOptionsArg(&m_openOptions);
AddInputFormatsArg(&m_inputFormats)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
AddInputDatasetArg(&m_inputDataset, openForMixedRasterVector
? GDAL_OF_RASTER | GDAL_OF_VECTOR |
GDAL_OF_MULTIDIM_RASTER
: GDAL_OF_RASTER);
AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER);
AddCreationOptionsArg(&m_creationOptions);
const char *exclusionGroup = "overwrite-append";
AddOverwriteArg(&m_overwrite).SetMutualExclusionGroup(exclusionGroup);
AddArg("append", 0, _("Append as a subdataset to existing output"),
&m_append)
.SetDefault(false)
.SetMutualExclusionGroup(exclusionGroup);
}
/************************************************************************/
/* GDALRasterConvertAlgorithm::RunImpl() */
/************************************************************************/
bool GDALRasterConvertAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
void *pProgressData)
{
CPLAssert(m_inputDataset.GetDataset());
if (m_outputDataset.GetDataset())
{
CPLError(CE_Failure, CPLE_NotSupported,
"gdal raster convert does not support outputting to an "
"already opened output dataset");
return false;
}
CPLStringList aosOptions;
if (!m_outputFormat.empty())
{
aosOptions.AddString("-of");
aosOptions.AddString(m_outputFormat.c_str());
}
if (!m_overwrite)
{
aosOptions.AddString("--no-overwrite");
}
if (m_append)
{
aosOptions.AddString("-co");
aosOptions.AddString("APPEND_SUBDATASET=YES");
}
for (const auto &co : m_creationOptions)
{
aosOptions.AddString("-co");
aosOptions.AddString(co.c_str());
}
GDALTranslateOptions *psOptions =
GDALTranslateOptionsNew(aosOptions.List(), nullptr);
GDALTranslateOptionsSetProgress(psOptions, pfnProgress, pProgressData);
auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
GDALTranslate(m_outputDataset.GetName().c_str(),
GDALDataset::ToHandle(m_inputDataset.GetDataset()),
psOptions, nullptr)));
GDALTranslateOptionsFree(psOptions);
if (!poOutDS)
return false;
m_outputDataset.Set(std::move(poOutDS));
return true;
}
//! @endcond

View File

@ -0,0 +1,65 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster convert" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_RASTER_CONVERT_INCLUDED
#define GDALALG_RASTER_CONVERT_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterConvertAlgorithm */
/************************************************************************/
class GDALRasterConvertAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "convert";
static constexpr const char *DESCRIPTION = "Convert a raster dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
explicit GDALRasterConvertAlgorithm(bool openForMixedRasterVector = false);
GDALDataset *GetDataset()
{
return m_inputDataset.GetDataset();
}
void SetDataset(GDALDataset *poDS, bool owned)
{
auto arg = GetArg(GDAL_ARG_NAME_INPUT);
arg->Set(poDS, owned);
arg->SetSkipIfAlreadySet();
}
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::string m_outputFormat{};
GDALArgDatasetValue m_inputDataset{};
std::vector<std::string> m_openOptions{};
std::vector<std::string> m_inputFormats{};
GDALArgDatasetValue m_outputDataset{};
std::vector<std::string> m_creationOptions{};
bool m_overwrite = false;
bool m_append = false;
};
//! @endcond
#endif

View File

@ -0,0 +1,178 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster info" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_raster_info.h"
#include "cpl_conv.h"
#include "gdal_priv.h"
#include "gdal_utils.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALRasterInfoAlgorithm::GDALRasterInfoAlgorithm() */
/************************************************************************/
GDALRasterInfoAlgorithm::GDALRasterInfoAlgorithm(bool openForMixedRasterVector)
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddOutputFormatArg(&m_format).SetDefault("json").SetChoices("json", "text");
AddArg("min-max", 0, _("Compute minimum and maximum value"), &m_minMax)
.AddAlias("mm");
AddArg("stats", 0, _("Retrieve or compute statistics, using all pixels"),
&m_stats)
.SetMutualExclusionGroup("stats");
AddArg("approx-stats", 0,
_("Retrieve or compute statistics, using a subset of pixels"),
&m_approxStats)
.SetMutualExclusionGroup("stats");
AddArg("hist", 0, _("Retrieve or compute histogram"), &m_hist);
AddOpenOptionsArg(&m_openOptions);
AddInputFormatsArg(&m_inputFormats)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
AddArg("no-gcp", 0, _("Suppress ground control points list printing"),
&m_noGCP)
.SetCategory(GAAC_ADVANCED);
AddArg("no-md", 0, _("Suppress metadata printing"), &m_noMD)
.SetCategory(GAAC_ADVANCED);
AddArg("no-ct", 0, _("Suppress color table printing"), &m_noCT)
.SetCategory(GAAC_ADVANCED);
AddArg("no-fl", 0, _("Suppress file list printing"), &m_noFL)
.SetCategory(GAAC_ADVANCED);
AddArg("checksum", 0, _("Compute pixel checksum"), &m_checksum)
.SetCategory(GAAC_ADVANCED);
AddArg("list-mdd", 0,
_("List all metadata domains available for the dataset"), &m_listMDD)
.SetCategory(GAAC_ADVANCED);
AddArg("mdd", 0,
_("Report metadata for the specified domain. 'all' can be used to "
"report metadata in all domains"),
&m_mdd)
.SetCategory(GAAC_ADVANCED);
AddArg("no-nodata", 0, _("Suppress retrieving nodata value"), &m_noNodata)
.SetCategory(GAAC_ESOTERIC);
AddArg("no-mask", 0, _("Suppress mask band information"), &m_noMask)
.SetCategory(GAAC_ESOTERIC);
AddArg("subdataset", 0,
_("Use subdataset of specified index (starting at 1), instead of "
"the source dataset itself"),
&m_subDS)
.SetCategory(GAAC_ESOTERIC);
AddInputDatasetArg(&m_dataset, openForMixedRasterVector
? GDAL_OF_RASTER | GDAL_OF_VECTOR |
GDAL_OF_MULTIDIM_RASTER
: GDAL_OF_RASTER);
AddOutputStringArg(&m_output);
AddArg("stdout", 0,
_("Directly output on stdout (format=text mode only). If enabled, "
"output-string will be empty"),
&m_stdout)
.SetHiddenForCLI();
}
/************************************************************************/
/* GDALRasterInfoAlgorithm::RunImpl() */
/************************************************************************/
bool GDALRasterInfoAlgorithm::RunImpl(GDALProgressFunc, void *)
{
CPLAssert(m_dataset.GetDataset());
CPLStringList aosOptions;
if (m_format == "json")
aosOptions.AddString("-json");
if (m_minMax)
aosOptions.AddString("-mm");
if (m_stats)
aosOptions.AddString("-stats");
if (m_approxStats)
aosOptions.AddString("-approx_stats");
if (m_hist)
aosOptions.AddString("-hist");
if (m_noGCP)
aosOptions.AddString("-nogcp");
if (m_noMD)
aosOptions.AddString("-nomd");
if (m_noCT)
aosOptions.AddString("-noct");
if (m_noFL)
aosOptions.AddString("-nofl");
if (m_noMask)
aosOptions.AddString("-nomask");
if (m_noNodata)
aosOptions.AddString("-nonodata");
if (m_checksum)
aosOptions.AddString("-checksum");
if (m_listMDD)
aosOptions.AddString("-listmdd");
if (!m_mdd.empty())
{
aosOptions.AddString("-mdd");
aosOptions.AddString(m_mdd.c_str());
}
GDALDatasetH hDS = GDALDataset::ToHandle(m_dataset.GetDataset());
std::unique_ptr<GDALDataset> poSubDataset;
if (m_subDS > 0)
{
char **papszSubdatasets = GDALGetMetadata(hDS, "SUBDATASETS");
const int nSubdatasets = CSLCount(papszSubdatasets) / 2;
if (m_subDS > nSubdatasets)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Invalid value for 'subdataset' argument. Should be "
"between 1 and %d",
nSubdatasets);
return false;
}
char szKeyName[64];
snprintf(szKeyName, sizeof(szKeyName), "SUBDATASET_%d_NAME", m_subDS);
const std::string osSubDSName =
CSLFetchNameValueDef(papszSubdatasets, szKeyName, "");
poSubDataset.reset(GDALDataset::Open(
osSubDSName.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
nullptr, nullptr, nullptr));
if (!poSubDataset)
return false;
hDS = GDALDataset::ToHandle(poSubDataset.get());
}
if (m_stdout)
{
aosOptions.AddString("-stdout");
}
GDALInfoOptions *psOptions = GDALInfoOptionsNew(aosOptions.List(), nullptr);
char *ret = GDALInfo(hDS, psOptions);
GDALInfoOptionsFree(psOptions);
const bool bOK = ret != nullptr;
if (ret)
{
m_output = ret;
}
CPLFree(ret);
return bOK;
}
//! @endcond

View File

@ -0,0 +1,78 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster info" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_RASTER_INFO_INCLUDED
#define GDALALG_RASTER_INFO_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterInfoAlgorithm */
/************************************************************************/
class GDALRasterInfoAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "info";
static constexpr const char *DESCRIPTION =
"Return information on a raster dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
explicit GDALRasterInfoAlgorithm(bool openForMixedRasterVector = false);
GDALDataset *GetDataset()
{
return m_dataset.GetDataset();
}
void SetDataset(GDALDataset *poDS, bool owned)
{
auto arg = GetArg(GDAL_ARG_NAME_INPUT);
arg->Set(poDS, owned);
arg->SetSkipIfAlreadySet();
}
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::string m_format{};
bool m_minMax = false;
bool m_stats = false;
bool m_approxStats = false;
bool m_hist = false;
bool m_noGCP = false;
bool m_noMD = false;
bool m_noCT = false;
bool m_noFL = false;
bool m_noMask = false;
bool m_noNodata = false;
bool m_checksum = false;
bool m_listMDD = false;
bool m_stdout = false;
std::string m_mdd{};
int m_subDS = 0;
GDALArgDatasetValue m_dataset{};
std::vector<std::string> m_openOptions{};
std::vector<std::string> m_inputFormats{};
std::string m_output{};
};
//! @endcond
#endif

52
apps/gdalalg_vector.cpp Normal file
View File

@ -0,0 +1,52 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalgorithm.h"
#include "gdalalg_vector_info.h"
#include "gdalalg_vector_convert.h"
#include "gdalalg_vector_pipeline.h"
/************************************************************************/
/* GDALVectorAlgorithm */
/************************************************************************/
class GDALVectorAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "vector";
static constexpr const char *DESCRIPTION = "Vector commands.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
RegisterSubAlgorithm<GDALVectorInfoAlgorithm>();
RegisterSubAlgorithm<GDALVectorConvertAlgorithm>();
RegisterSubAlgorithm<GDALVectorPipelineAlgorithm>();
}
private:
bool RunImpl(GDALProgressFunc, void *) override
{
CPLError(CE_Failure, CPLE_AppDefined,
"The Run() method should not be called directly on the \"gdal "
"vector\" program.");
return false;
}
};
GDAL_STATIC_REGISTER_ALG(GDALVectorAlgorithm);

View File

@ -0,0 +1,136 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector convert" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_convert.h"
#include "cpl_conv.h"
#include "gdal_priv.h"
#include "gdal_utils.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALVectorConvertAlgorithm::GDALVectorConvertAlgorithm() */
/************************************************************************/
GDALVectorConvertAlgorithm::GDALVectorConvertAlgorithm()
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddProgressArg();
AddOutputFormatArg(&m_outputFormat)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
{GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
AddOpenOptionsArg(&m_openOptions);
AddInputFormatsArg(&m_inputFormats)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR});
AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR);
AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR);
m_outputDataset.SetInputFlags(GADV_NAME | GADV_OBJECT);
AddCreationOptionsArg(&m_creationOptions);
AddLayerCreationOptionsArg(&m_layerCreationOptions);
AddOverwriteArg(&m_overwrite);
AddUpdateArg(&m_update);
AddArg("overwrite-layer", 0,
_("Whether overwriting existing layer is allowed"),
&m_overwriteLayer)
.SetDefault(false);
AddArg("append", 0, _("Whether appending to existing layer is allowed"),
&m_appendLayer)
.SetDefault(false);
AddArg("input-layer", 'l', _("Input layer name(s)"), &m_inputLayerNames)
.AddAlias("layer");
AddArg("output-layer", 0, _("Output layer name"), &m_outputLayerName)
.AddHiddenAlias("nln"); // For ogr2ogr nostalgic people
}
/************************************************************************/
/* GDALVectorConvertAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorConvertAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
void *pProgressData)
{
CPLAssert(m_inputDataset.GetDataset());
CPLStringList aosOptions;
aosOptions.AddString("--invoked-from-gdal-vector-convert");
if (!m_overwrite)
{
aosOptions.AddString("--no-overwrite");
}
if (m_overwriteLayer)
{
aosOptions.AddString("-overwrite");
}
if (m_appendLayer)
{
aosOptions.AddString("-append");
}
if (!m_outputFormat.empty())
{
aosOptions.AddString("-of");
aosOptions.AddString(m_outputFormat.c_str());
}
for (const auto &co : m_creationOptions)
{
aosOptions.AddString("-dsco");
aosOptions.AddString(co.c_str());
}
for (const auto &co : m_layerCreationOptions)
{
aosOptions.AddString("-lco");
aosOptions.AddString(co.c_str());
}
if (!m_outputLayerName.empty())
{
aosOptions.AddString("-nln");
aosOptions.AddString(m_outputLayerName.c_str());
}
if (pfnProgress && pfnProgress != GDALDummyProgress)
{
aosOptions.AddString("-progress");
}
// Must be last, as positional
for (const auto &name : m_inputLayerNames)
{
aosOptions.AddString(name.c_str());
}
GDALVectorTranslateOptions *psOptions =
GDALVectorTranslateOptionsNew(aosOptions.List(), nullptr);
GDALVectorTranslateOptionsSetProgress(psOptions, pfnProgress,
pProgressData);
GDALDatasetH hOutDS = GDALDataset::ToHandle(m_outputDataset.GetDataset());
GDALDatasetH hSrcDS = GDALDataset::ToHandle(m_inputDataset.GetDataset());
auto poRetDS = GDALDataset::FromHandle(
GDALVectorTranslate(m_outputDataset.GetName().c_str(), hOutDS, 1,
&hSrcDS, psOptions, nullptr));
GDALVectorTranslateOptionsFree(psOptions);
if (!poRetDS)
return false;
if (!hOutDS)
{
m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
}
return true;
}
//! @endcond

View File

@ -0,0 +1,68 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector convert" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_CONVERT_INCLUDED
#define GDALALG_VECTOR_CONVERT_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorConvertAlgorithm */
/************************************************************************/
class GDALVectorConvertAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "convert";
static constexpr const char *DESCRIPTION = "Convert a vector dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorConvertAlgorithm();
void SetDataset(GDALDataset *poDS, bool owned)
{
auto arg = GetArg(GDAL_ARG_NAME_INPUT);
if (arg)
{
arg->Set(poDS, owned);
arg->SetSkipIfAlreadySet();
}
}
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::string m_outputFormat{};
GDALArgDatasetValue m_inputDataset{};
std::vector<std::string> m_openOptions{};
std::vector<std::string> m_inputFormats{};
GDALArgDatasetValue m_outputDataset{};
std::vector<std::string> m_creationOptions{};
std::vector<std::string> m_layerCreationOptions{};
bool m_overwrite = false;
bool m_update = false;
bool m_overwriteLayer = false;
bool m_appendLayer = false;
std::vector<std::string> m_inputLayerNames{};
std::string m_outputLayerName{};
};
//! @endcond
#endif

View File

@ -0,0 +1,86 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "filter" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_filter.h"
#include "gdal_priv.h"
#include "ogrsf_frmts.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm() */
/************************************************************************/
GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm()
: GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
auto &arg =
AddArg("bbox", 0, _("Bounding box as xmin,ymin,xmax,ymax"), &m_bbox)
.SetRepeatedArgAllowed(false)
.SetMinCount(4)
.SetMaxCount(4)
.SetDisplayHintAboutRepetition(false);
arg.AddValidationAction(
[&arg]()
{
const auto &val = arg.Get<std::vector<double>>();
CPLAssert(val.size() == 4);
if (!(val[0] <= val[2]) || !(val[1] <= val[3]))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Value of 'bbox' should be xmin,ymin,xmax,ymax with "
"xmin <= xmax and ymin <= ymax");
return false;
}
return true;
});
}
/************************************************************************/
/* GDALVectorFilterAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorFilterAlgorithm::RunImpl(GDALProgressFunc, void *)
{
CPLAssert(m_inputDataset.GetDataset());
CPLAssert(m_outputDataset.GetName().empty());
CPLAssert(!m_outputDataset.GetDataset());
bool ret = true;
if (m_bbox.size() == 4)
{
const double xmin = m_bbox[0];
const double ymin = m_bbox[1];
const double xmax = m_bbox[2];
const double ymax = m_bbox[3];
auto poSrcDS = m_inputDataset.GetDataset();
const int nLayerCount = poSrcDS->GetLayerCount();
for (int i = 0; i < nLayerCount; ++i)
{
auto poSrcLayer = poSrcDS->GetLayer(i);
ret = ret && (poSrcLayer != nullptr);
if (poSrcLayer)
poSrcLayer->SetSpatialFilterRect(xmin, ymin, xmax, ymax);
}
}
m_outputDataset.Set(m_inputDataset.GetDataset(), false);
return true;
}
//! @endcond

View File

@ -0,0 +1,46 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "filter" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_FILTER_INCLUDED
#define GDALALG_VECTOR_FILTER_INCLUDED
#include "gdalalg_vector_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorFilterAlgorithm */
/************************************************************************/
class GDALVectorFilterAlgorithm final : public GDALVectorPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "filter";
static constexpr const char *DESCRIPTION = "Filter.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorFilterAlgorithm();
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::vector<double> m_bbox{};
};
//! @endcond
#endif /* GDALALG_VECTOR_FILTER_INCLUDED */

View File

@ -0,0 +1,128 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector info" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_info.h"
#include "cpl_conv.h"
#include "gdal_priv.h"
#include "gdal_utils.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALVectorInfoAlgorithm::GDALVectorInfoAlgorithm() */
/************************************************************************/
GDALVectorInfoAlgorithm::GDALVectorInfoAlgorithm()
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddOutputFormatArg(&m_format).SetDefault("json").SetChoices("json", "text");
AddOpenOptionsArg(&m_openOptions);
AddInputFormatsArg(&m_inputFormats)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR});
AddInputDatasetArg(&m_dataset, GDAL_OF_VECTOR);
AddLayerNameArg(&m_layerNames).SetMutualExclusionGroup("layer-sql");
AddArg("features", 0,
_("List all features (beware of RAM consumption on large layers)"),
&m_listFeatures);
AddArg("sql", 0,
_("Execute the indicated SQL statement and return the result"),
&m_sql)
.SetReadFromFileAtSyntaxAllowed()
.SetMetaVar("<statement>|@<filename>")
.SetRemoveSQLCommentsEnabled()
.SetMutualExclusionGroup("layer-sql");
AddArg("where", 0,
_("Attribute query in a restricted form of the queries used in the "
"SQL WHERE statement"),
&m_where)
.SetReadFromFileAtSyntaxAllowed()
.SetMetaVar("<WHERE>|@<filename>")
.SetRemoveSQLCommentsEnabled();
AddArg("dialect", 0, _("SQL dialect"), &m_dialect);
AddArg(GDAL_ARG_NAME_UPDATE, 0, _("Open the dataset in update mode"),
&m_update);
AddOutputStringArg(&m_output);
AddArg("stdout", 0,
_("Directly output on stdout (format=text mode only). If enabled, "
"output-string will be empty"),
&m_stdout)
.SetHiddenForCLI();
}
/************************************************************************/
/* GDALVectorInfoAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorInfoAlgorithm::RunImpl(GDALProgressFunc, void *)
{
CPLAssert(m_dataset.GetDataset());
CPLStringList aosOptions;
if (m_format == "json")
{
aosOptions.AddString("-json");
}
else
{
aosOptions.AddString("-al");
if (!m_listFeatures)
aosOptions.AddString("-so");
}
if (m_listFeatures)
{
aosOptions.AddString("-features");
}
if (!m_sql.empty())
{
aosOptions.AddString("-sql");
aosOptions.AddString(m_sql.c_str());
}
if (!m_where.empty())
{
aosOptions.AddString("-where");
aosOptions.AddString(m_where.c_str());
}
if (!m_dialect.empty())
{
aosOptions.AddString("-dialect");
aosOptions.AddString(m_dialect.c_str());
}
if (m_stdout)
{
aosOptions.AddString("-stdout");
}
// Must be last, as positional
aosOptions.AddString("dummy");
for (const std::string &name : m_layerNames)
aosOptions.AddString(name.c_str());
GDALVectorInfoOptions *psInfo =
GDALVectorInfoOptionsNew(aosOptions.List(), nullptr);
char *ret =
GDALVectorInfo(GDALDataset::ToHandle(m_dataset.GetDataset()), psInfo);
GDALVectorInfoOptionsFree(psInfo);
if (!ret)
return false;
m_output = ret;
CPLFree(ret);
return true;
}
//! @endcond

View File

@ -0,0 +1,68 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector info" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_INFO_INCLUDED
#define GDALALG_VECTOR_INFO_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorInfoAlgorithm */
/************************************************************************/
class GDALVectorInfoAlgorithm final : public GDALAlgorithm
{
public:
static constexpr const char *NAME = "info";
static constexpr const char *DESCRIPTION =
"Return information on a vector dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorInfoAlgorithm();
void SetDataset(GDALDataset *poDS, bool owned)
{
auto arg = GetArg(GDAL_ARG_NAME_INPUT);
if (arg)
{
arg->Set(poDS, owned);
arg->SetSkipIfAlreadySet();
}
}
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::string m_format{};
GDALArgDatasetValue m_dataset{};
std::vector<std::string> m_openOptions{};
std::vector<std::string> m_inputFormats{};
bool m_update = false;
std::vector<std::string> m_layerNames{};
bool m_listFeatures = false;
bool m_stdout = false;
std::string m_sql{};
std::string m_where{};
std::string m_dialect{};
std::string m_output{};
};
//! @endcond
#endif

View File

@ -0,0 +1,537 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector pipeline" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_pipeline.h"
#include "gdalalg_vector_read.h"
#include "gdalalg_vector_filter.h"
#include "gdalalg_vector_reproject.h"
#include "gdalalg_vector_write.h"
#include "cpl_conv.h"
#include "cpl_json.h"
#include "cpl_string.h"
#include <algorithm>
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALVectorPipelineStepAlgorithm::AddInputArgs() */
/************************************************************************/
void GDALVectorPipelineStepAlgorithm::AddInputArgs(bool hiddenForCLI)
{
AddInputFormatsArg(&m_inputFormats)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR})
.SetHiddenForCLI(hiddenForCLI);
AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR,
/* positionalAndRequired = */ !hiddenForCLI)
.SetHiddenForCLI(hiddenForCLI);
AddArg("input-layer", 'l', _("Input layer name(s)"), &m_inputLayerNames)
.AddAlias("layer")
.SetHiddenForCLI(hiddenForCLI);
}
/************************************************************************/
/* GDALVectorPipelineStepAlgorithm::AddOutputArgs() */
/************************************************************************/
void GDALVectorPipelineStepAlgorithm::AddOutputArgs(
bool hiddenForCLI, bool shortNameOutputLayerAllowed)
{
AddOutputFormatArg(&m_format)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
{GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
.SetHiddenForCLI(hiddenForCLI);
AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR,
/* positionalAndRequired = */ !hiddenForCLI)
.SetHiddenForCLI(hiddenForCLI);
m_outputDataset.SetInputFlags(GADV_NAME | GADV_OBJECT);
AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
AddLayerCreationOptionsArg(&m_layerCreationOptions)
.SetHiddenForCLI(hiddenForCLI);
AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
AddUpdateArg(&m_update).SetHiddenForCLI(hiddenForCLI);
AddArg("overwrite-layer", 0,
_("Whether overwriting existing layer is allowed"),
&m_overwriteLayer)
.SetDefault(false)
.SetHiddenForCLI(hiddenForCLI);
AddArg("append", 0, _("Whether appending to existing layer is allowed"),
&m_appendLayer)
.SetDefault(false)
.SetHiddenForCLI(hiddenForCLI);
AddArg("output-layer", shortNameOutputLayerAllowed ? 'l' : 0,
_("Output layer name"), &m_outputLayerName)
.AddHiddenAlias("nln") // For ogr2ogr nostalgic people
.SetHiddenForCLI(hiddenForCLI);
}
/************************************************************************/
/* GDALVectorPipelineAlgorithm::GDALVectorPipelineAlgorithm() */
/************************************************************************/
GDALVectorPipelineAlgorithm::GDALVectorPipelineAlgorithm()
: GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddInputArgs(/* hiddenForCLI = */ true);
AddProgressArg();
AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
.SetHiddenForCLI()
.SetPositional();
AddOutputArgs(/* hiddenForCLI = */ true,
/* shortNameOutputLayerAllowed=*/false);
m_stepRegistry.Register<GDALVectorReadAlgorithm>();
m_stepRegistry.Register<GDALVectorWriteAlgorithm>();
m_stepRegistry.Register<GDALVectorReprojectAlgorithm>();
m_stepRegistry.Register<GDALVectorFilterAlgorithm>();
}
/************************************************************************/
/* GDALVectorPipelineAlgorithm::GetStepAlg() */
/************************************************************************/
std::unique_ptr<GDALVectorPipelineStepAlgorithm>
GDALVectorPipelineAlgorithm::GetStepAlg(const std::string &name) const
{
auto alg = m_stepRegistry.Instantiate(name);
return std::unique_ptr<GDALVectorPipelineStepAlgorithm>(
cpl::down_cast<GDALVectorPipelineStepAlgorithm *>(alg.release()));
}
/************************************************************************/
/* GDALVectorPipelineAlgorithm::ParseCommandLineArguments() */
/************************************************************************/
bool GDALVectorPipelineAlgorithm::ParseCommandLineArguments(
const std::vector<std::string> &args)
{
if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
args[0] == "help" || args[0] == "--json-usage"))
return GDALAlgorithm::ParseCommandLineArguments(args);
for (const auto &arg : args)
{
if (arg.find("--pipeline") == 0)
return GDALAlgorithm::ParseCommandLineArguments(args);
// gdal vector pipeline [--progress] "read poly.gpkg ..."
if (arg.find("read ") == 0)
return GDALAlgorithm::ParseCommandLineArguments(args);
}
if (!m_steps.empty())
{
ReportError(CE_Failure, CPLE_AppDefined,
"ParseCommandLineArguments() can only be called once per "
"instance.");
return false;
}
struct Step
{
std::unique_ptr<GDALVectorPipelineStepAlgorithm> alg{};
std::vector<std::string> args{};
};
std::vector<Step> steps;
steps.resize(1);
for (const auto &arg : args)
{
if (arg == "--progress")
{
m_progressBarRequested = true;
continue;
}
auto &curStep = steps.back();
if (arg == "!" || arg == "|")
{
if (curStep.alg)
{
steps.resize(steps.size() + 1);
}
}
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
else if (arg == "+step")
{
if (curStep.alg)
{
steps.resize(steps.size() + 1);
}
}
else if (arg.find("+gdal=") == 0)
{
const std::string stepName = arg.substr(strlen("+gdal="));
curStep.alg = GetStepAlg(stepName);
if (!curStep.alg)
{
ReportError(CE_Failure, CPLE_AppDefined,
"unknown step name: %s", stepName.c_str());
return false;
}
}
#endif
else if (!curStep.alg)
{
std::string algName = arg;
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
if (!algName.empty() && algName[0] == '+')
algName = algName.substr(1);
#endif
curStep.alg = GetStepAlg(algName);
if (!curStep.alg)
{
ReportError(CE_Failure, CPLE_AppDefined,
"unknown step name: %s", algName.c_str());
return false;
}
}
else
{
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
if (!arg.empty() && arg[0] == '+')
{
curStep.args.push_back("--" + arg.substr(1));
continue;
}
#endif
std::string value = arg;
// #define GDAL_PIPELINE_NATURAL_LANGUAGE
#ifdef GDAL_PIPELINE_NATURAL_LANGUAGE
// gdal vector pipeline "read [from] poly.gpkg, reproject [from EPSG:4326] to EPSG:32632 and write to out.gpkg with overwriting"
if (value == "and")
{
steps.resize(steps.size() + 1);
}
else if (value == "from" && curStep.alg->GetName() == "read")
{
// do nothing
}
else if (value == "from" && curStep.alg->GetName() == "reproject")
{
curStep.args.push_back("--src-crs");
}
else if (value == "to" && curStep.alg->GetName() == "reproject")
{
curStep.args.push_back("--dst-crs");
}
else if (value == "to" && curStep.alg->GetName() == "write")
{
// do nothing
}
else if (value == "with" && curStep.alg->GetName() == "write")
{
// do nothing
}
else if (value == "overwriting" &&
curStep.alg->GetName() == "write")
{
curStep.args.push_back("--overwrite");
}
else if (!value.empty() && value.back() == ',')
{
curStep.args.push_back(value.substr(0, value.size() - 1));
steps.resize(steps.size() + 1);
}
else
#endif
{
curStep.args.push_back(value);
}
}
}
// As we initially added a step without alg to bootstrap things, make
// sure to remove it if it hasn't been filled, or the user has terminated
// the pipeline with a '!' separator.
if (!steps.back().alg)
steps.pop_back();
if (steps.size() < 2)
{
ReportError(CE_Failure, CPLE_AppDefined,
"At least 2 steps must be provided");
return false;
}
if (steps.front().alg->GetName() != GDALVectorReadAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined, "First step should be '%s'",
GDALVectorReadAlgorithm::NAME);
return false;
}
for (size_t i = 1; i < steps.size() - 1; ++i)
{
if (steps[i].alg->GetName() == GDALVectorReadAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Only first step can be '%s'",
GDALVectorReadAlgorithm::NAME);
return false;
}
}
if (steps.back().alg->GetName() != GDALVectorWriteAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
GDALVectorWriteAlgorithm::NAME);
return false;
}
for (size_t i = 0; i < steps.size() - 1; ++i)
{
if (steps[i].alg->GetName() == GDALVectorWriteAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Only last step can be '%s'",
GDALVectorWriteAlgorithm::NAME);
return false;
}
}
if (!m_pipeline.empty())
{
// Propagate input parameters set at the pipeline level to the
// "read" step
{
auto &step = steps.front();
for (auto &arg : step.alg->GetArgs())
{
auto pipelineArg = GetArg(arg->GetName());
if (pipelineArg && pipelineArg->IsExplicitlySet())
{
arg->SetSkipIfAlreadySet(true);
arg->SetFrom(*pipelineArg);
}
}
}
// Same with "write" step
{
auto &step = steps.back();
for (auto &arg : step.alg->GetArgs())
{
auto pipelineArg = GetArg(arg->GetName());
if (pipelineArg && pipelineArg->IsExplicitlySet())
{
arg->SetSkipIfAlreadySet(true);
arg->SetFrom(*pipelineArg);
}
}
}
}
// Parse each step, but without running the validation
for (const auto &step : steps)
{
step.alg->m_skipValidationInParseCommandLine = true;
if (!step.alg->ParseCommandLineArguments(step.args))
return false;
}
// Evaluate "input" argument of "read" step, together with the "output"
// argument of the "write" step, in case they point to the same dataset.
auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
if (inputArg && inputArg->IsExplicitlySet() &&
inputArg->GetType() == GAAT_DATASET)
{
steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
}
for (const auto &step : steps)
{
if (!step.alg->ValidateArguments())
return false;
}
for (auto &step : steps)
m_steps.push_back(std::move(step.alg));
return true;
}
/************************************************************************/
/* GDALVectorPipelineAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorPipelineAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
void *pProgressData)
{
if (m_steps.empty())
{
// If invoked programmatically, not from the command line.
if (m_pipeline.empty())
{
ReportError(CE_Failure, CPLE_AppDefined,
"'pipeline' argument not set");
return false;
}
const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
if (!ParseCommandLineArguments(aosTokens))
return false;
}
GDALDataset *poCurDS = nullptr;
for (size_t i = 0; i < m_steps.size(); ++i)
{
auto &step = m_steps[i];
if (i > 0)
{
if (step->m_inputDataset.GetDataset())
{
// Shouldn't happen
ReportError(CE_Failure, CPLE_AppDefined,
"Step nr %d (%s) has already an input dataset",
static_cast<int>(i), step->GetName().c_str());
return false;
}
step->m_inputDataset.Set(poCurDS, false);
}
if (i + 1 < m_steps.size() && step->m_outputDataset.GetDataset())
{
// Shouldn't happen
ReportError(CE_Failure, CPLE_AppDefined,
"Step nr %d (%s) has already an output dataset",
static_cast<int>(i), step->GetName().c_str());
return false;
}
if (!step->Run(i < m_steps.size() - 1 ? nullptr : pfnProgress,
i < m_steps.size() - 1 ? nullptr : pProgressData))
{
return false;
}
poCurDS = step->m_outputDataset.GetDataset();
if (!poCurDS)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Step nr %d (%s) failed to produce an output dataset",
static_cast<int>(i), step->GetName().c_str());
return false;
}
}
if (!m_outputDataset.GetDataset())
{
m_outputDataset.Set(poCurDS, false);
}
return true;
}
/************************************************************************/
/* GDALAlgorithm::Finalize() */
/************************************************************************/
bool GDALVectorPipelineAlgorithm::Finalize()
{
bool ret = GDALAlgorithm::Finalize();
for (auto &step : m_steps)
{
ret = step->Finalize() && ret;
}
return ret;
}
/************************************************************************/
/* GDALVectorPipelineAlgorithm::GetUsageForCLI() */
/************************************************************************/
std::string GDALVectorPipelineAlgorithm::GetUsageForCLI(
bool shortUsage, const UsageOptions &usageOptions) const
{
std::string ret = GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
if (shortUsage)
return ret;
ret += "\n<PIPELINE> is of the form: read [READ-OPTIONS] "
"( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]\n";
ret += '\n';
ret += "Example: 'gdal vector pipeline --progress ! read in.gpkg ! \\\n";
ret += " reproject --dst-crs=EPSG:32632 ! ";
ret += "write out.gpkg --overwrite'\n";
ret += '\n';
ret += "Potential steps are:\n";
UsageOptions stepUsageOptions;
stepUsageOptions.isPipelineStep = true;
for (const std::string &name : m_stepRegistry.GetNames())
{
auto alg = GetStepAlg(name);
auto [options, maxOptLen] = alg->GetArgNamesForCLI();
stepUsageOptions.maxOptLen =
std::max(stepUsageOptions.maxOptLen, maxOptLen);
}
{
const auto name = GDALVectorReadAlgorithm::NAME;
ret += '\n';
auto alg = GetStepAlg(name);
alg->SetCallPath({name});
ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
}
for (const std::string &name : m_stepRegistry.GetNames())
{
if (name != GDALVectorReadAlgorithm::NAME &&
name != GDALVectorWriteAlgorithm::NAME)
{
ret += '\n';
auto alg = GetStepAlg(name);
alg->SetCallPath({name});
ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
}
}
{
const auto name = GDALVectorWriteAlgorithm::NAME;
ret += '\n';
auto alg = GetStepAlg(name);
alg->SetCallPath({name});
ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
}
return ret;
}
/************************************************************************/
/* GDALVectorPipelineAlgorithm::GetUsageAsJSON() */
/************************************************************************/
std::string GDALVectorPipelineAlgorithm::GetUsageAsJSON() const
{
CPLJSONDocument oDoc;
oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON());
CPLJSONArray jPipelineSteps;
for (const std::string &name : m_stepRegistry.GetNames())
{
auto alg = GetStepAlg(name);
CPLJSONDocument oStepDoc;
oStepDoc.LoadMemory(alg->GetUsageAsJSON());
jPipelineSteps.Add(oStepDoc.GetRoot());
}
oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
return oDoc.SaveAsString();
}
//! @endcond

View File

@ -0,0 +1,116 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "vector pipeline" subcommand
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_PIPELINE_INCLUDED
#define GDALALG_VECTOR_PIPELINE_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorPipelineStepAlgorithm */
/************************************************************************/
class GDALVectorPipelineStepAlgorithm /* non final */ : public GDALAlgorithm
{
protected:
GDALVectorPipelineStepAlgorithm(const std::string &name,
const std::string &description,
const std::string &helpURL)
: GDALAlgorithm(name, description, helpURL)
{
}
friend class GDALVectorPipelineAlgorithm;
void AddInputArgs(bool hiddenForCLI);
void AddOutputArgs(bool hiddenForCLI, bool shortNameOutputLayerAllowed);
// Input arguments
GDALArgDatasetValue m_inputDataset{};
std::vector<std::string> m_openOptions{};
std::vector<std::string> m_inputFormats{};
std::vector<std::string> m_inputLayerNames{};
// Output arguments
GDALArgDatasetValue m_outputDataset{};
std::string m_format{};
std::vector<std::string> m_creationOptions{};
std::vector<std::string> m_layerCreationOptions{};
bool m_overwrite = false;
bool m_update = false;
bool m_overwriteLayer = false;
bool m_appendLayer = false;
std::string m_outputLayerName{};
};
/************************************************************************/
/* GDALVectorPipelineAlgorithm */
/************************************************************************/
// This is an easter egg to pay tribute to PROJ pipeline syntax
// We accept "gdal vector +gdal=pipeline +step +gdal=read +input=poly.gpkg +step +gdal=reproject +dst-crs=EPSG:32632 +step +gdal=write +output=out.gpkg +overwrite"
// as an alternative to (recommended):
// "gdal vector pipeline ! read poly.gpkg ! reproject--dst-crs=EPSG:32632 ! write out.gpkg --overwrite"
#define GDAL_PIPELINE_PROJ_NOSTALGIA
class GDALVectorPipelineAlgorithm final : public GDALVectorPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "pipeline";
static constexpr const char *DESCRIPTION = "Process a vector dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
GDALAlgorithmRegistry::HIDDEN_ALIAS_SEPARATOR,
"+pipeline",
"+gdal=pipeline",
#endif
};
}
GDALVectorPipelineAlgorithm();
bool
ParseCommandLineArguments(const std::vector<std::string> &args) override;
bool Finalize() override;
std::string GetUsageForCLI(bool shortUsage,
const UsageOptions &usageOptions) const override;
std::string GetUsageAsJSON() const override;
/* cppcheck-suppress functionStatic */
void SetDataset(GDALDataset *, bool)
{
}
private:
std::string m_pipeline{};
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::unique_ptr<GDALVectorPipelineStepAlgorithm>
GetStepAlg(const std::string &name) const;
GDALAlgorithmRegistry m_stepRegistry{};
std::vector<std::unique_ptr<GDALVectorPipelineStepAlgorithm>> m_steps{};
};
//! @endcond
#endif

View File

@ -0,0 +1,100 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "read" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_read.h"
#include "gdal_priv.h"
#include "ogrsf_frmts.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALVectorReadAlgorithm::GDALVectorReadAlgorithm() */
/************************************************************************/
GDALVectorReadAlgorithm::GDALVectorReadAlgorithm()
: GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddInputArgs(/* hiddenForCLI = */ false);
}
/************************************************************************/
/* GDALVectorReadAlgorithmDataset */
/************************************************************************/
namespace
{
class GDALVectorReadAlgorithmDataset final : public GDALDataset
{
std::vector<OGRLayer *> m_srcLayers{};
public:
GDALVectorReadAlgorithmDataset() = default;
void AddLayer(OGRLayer *poSrcLayer)
{
m_srcLayers.push_back(poSrcLayer);
}
int GetLayerCount() override
{
return static_cast<int>(m_srcLayers.size());
}
OGRLayer *GetLayer(int idx) override
{
return idx >= 0 && idx < GetLayerCount() ? m_srcLayers[idx] : nullptr;
}
};
} // namespace
/************************************************************************/
/* GDALVectorReadAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorReadAlgorithm::RunImpl(GDALProgressFunc, void *)
{
CPLAssert(m_inputDataset.GetDataset());
CPLAssert(m_outputDataset.GetName().empty());
CPLAssert(!m_outputDataset.GetDataset());
if (m_inputLayerNames.empty())
{
m_outputDataset.Set(m_inputDataset.GetDataset(), false);
}
else
{
auto poSrcDS = m_inputDataset.GetDataset();
auto poOutDS = std::make_unique<GDALVectorReadAlgorithmDataset>();
poOutDS->SetDescription(poSrcDS->GetDescription());
for (const auto &srcLayerName : m_inputLayerNames)
{
auto poSrcLayer = poSrcDS->GetLayerByName(srcLayerName.c_str());
if (!poSrcLayer)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Cannot find source layer '%s'",
srcLayerName.c_str());
return false;
}
poOutDS->AddLayer(poSrcLayer);
}
m_outputDataset.Set(std::move(poOutDS));
}
return true;
}
//! @endcond

View File

@ -0,0 +1,44 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "read" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_READ_INCLUDED
#define GDALALG_VECTOR_READ_INCLUDED
#include "gdalalg_vector_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorReadAlgorithm */
/************************************************************************/
class GDALVectorReadAlgorithm final : public GDALVectorPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "read";
static constexpr const char *DESCRIPTION = "Read a vector dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorReadAlgorithm();
private:
bool RunImpl(GDALProgressFunc, void *) override;
};
//! @endcond
#endif

View File

@ -0,0 +1,148 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "reproject" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_reproject.h"
#include "gdal_priv.h"
#include "ogr_spatialref.h"
#include "ogrsf_frmts.h"
#include "ogrwarpedlayer.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALVectorReprojectAlgorithm::GDALVectorReprojectAlgorithm() */
/************************************************************************/
GDALVectorReprojectAlgorithm::GDALVectorReprojectAlgorithm()
: GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs).AddHiddenAlias("s_srs");
AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
.SetRequired()
.AddHiddenAlias("t_srs");
}
/************************************************************************/
/* GDALVectorReprojectAlgorithmDataset */
/************************************************************************/
namespace
{
class GDALVectorReprojectAlgorithmDataset final : public GDALDataset
{
std::vector<std::unique_ptr<OGRLayer>> m_layers{};
public:
GDALVectorReprojectAlgorithmDataset() = default;
void AddLayer(std::unique_ptr<OGRLayer> poLayer)
{
m_layers.push_back(std::move(poLayer));
}
int GetLayerCount() override
{
return static_cast<int>(m_layers.size());
}
OGRLayer *GetLayer(int idx) override
{
return idx >= 0 && idx < GetLayerCount() ? m_layers[idx].get()
: nullptr;
}
};
} // namespace
/************************************************************************/
/* GDALVectorReprojectAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorReprojectAlgorithm::RunImpl(GDALProgressFunc, void *)
{
CPLAssert(m_inputDataset.GetDataset());
CPLAssert(m_outputDataset.GetName().empty());
CPLAssert(!m_outputDataset.GetDataset());
std::unique_ptr<OGRSpatialReference> poSrcCRS;
if (!m_srsCrs.empty())
{
poSrcCRS = std::make_unique<OGRSpatialReference>();
if (poSrcCRS->SetFromUserInput(m_srsCrs.c_str()) != OGRERR_NONE)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Invalid value for '--src-crs'");
return false;
}
poSrcCRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
}
OGRSpatialReference oDstCRS;
if (oDstCRS.SetFromUserInput(m_dstCrs.c_str()) != OGRERR_NONE)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Invalid value for '--dst-crs'");
return false;
}
oDstCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
auto poSrcDS = m_inputDataset.GetDataset();
auto reprojectedDataset =
std::make_unique<GDALVectorReprojectAlgorithmDataset>();
reprojectedDataset->SetDescription(poSrcDS->GetDescription());
const int nLayerCount = poSrcDS->GetLayerCount();
bool ret = true;
for (int i = 0; i < nLayerCount; ++i)
{
auto poSrcLayer = poSrcDS->GetLayer(i);
ret = ret && (poSrcLayer != nullptr);
if (poSrcLayer)
{
OGRSpatialReference *poSrcLayerCRS;
if (poSrcCRS)
poSrcLayerCRS = poSrcCRS.get();
else
poSrcLayerCRS = poSrcLayer->GetSpatialRef();
if (!poSrcLayerCRS)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Layer '%s' has no spatial reference system",
poSrcLayer->GetName());
return false;
}
auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
OGRCreateCoordinateTransformation(poSrcLayerCRS, &oDstCRS));
auto poReversedCT = std::unique_ptr<OGRCoordinateTransformation>(
OGRCreateCoordinateTransformation(&oDstCRS, poSrcLayerCRS));
ret = ret && (poCT != nullptr) && (poReversedCT != nullptr);
if (poCT && poReversedCT)
{
reprojectedDataset->AddLayer(std::make_unique<OGRWarpedLayer>(
poSrcLayer, /* iGeomField = */ 0,
/*bTakeOwnership = */ false, poCT.release(),
poReversedCT.release()));
}
}
}
m_outputDataset.Set(std::move(reprojectedDataset));
return ret;
}
//! @endcond

View File

@ -0,0 +1,48 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "reproject" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_REPROJECT_INCLUDED
#define GDALALG_VECTOR_REPROJECT_INCLUDED
#include "gdalalg_vector_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorReprojectAlgorithm */
/************************************************************************/
class GDALVectorReprojectAlgorithm final
: public GDALVectorPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "reproject";
static constexpr const char *DESCRIPTION = "Reproject.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorReprojectAlgorithm();
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::string m_srsCrs{};
std::string m_dstCrs{};
};
//! @endcond
#endif /* GDALALG_VECTOR_REPROJECT_INCLUDED */

View File

@ -0,0 +1,106 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "write" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalg_vector_write.h"
#include "cpl_string.h"
#include "gdal_utils.h"
#include "gdal_priv.h"
#ifndef _
#define _(x) (x)
#endif
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorWriteAlgorithm::GDALVectorWriteAlgorithm() */
/************************************************************************/
GDALVectorWriteAlgorithm::GDALVectorWriteAlgorithm()
: GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL)
{
AddOutputArgs(/* hiddenForCLI = */ false,
/* shortNameOutputLayerAllowed=*/true);
}
/************************************************************************/
/* GDALVectorWriteAlgorithm::RunImpl() */
/************************************************************************/
bool GDALVectorWriteAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
void *pProgressData)
{
CPLAssert(m_inputDataset.GetDataset());
CPLStringList aosOptions;
aosOptions.AddString("--invoked-from-gdal-vector-convert");
if (!m_overwrite)
{
aosOptions.AddString("--no-overwrite");
}
if (m_overwriteLayer)
{
aosOptions.AddString("-overwrite");
}
if (m_appendLayer)
{
aosOptions.AddString("-append");
}
if (!m_format.empty())
{
aosOptions.AddString("-of");
aosOptions.AddString(m_format.c_str());
}
for (const auto &co : m_creationOptions)
{
aosOptions.AddString("-dsco");
aosOptions.AddString(co.c_str());
}
for (const auto &co : m_layerCreationOptions)
{
aosOptions.AddString("-lco");
aosOptions.AddString(co.c_str());
}
if (!m_outputLayerName.empty())
{
aosOptions.AddString("-nln");
aosOptions.AddString(m_outputLayerName.c_str());
}
if (pfnProgress && pfnProgress != GDALDummyProgress)
{
aosOptions.AddString("-progress");
}
GDALVectorTranslateOptions *psOptions =
GDALVectorTranslateOptionsNew(aosOptions.List(), nullptr);
GDALVectorTranslateOptionsSetProgress(psOptions, pfnProgress,
pProgressData);
GDALDatasetH hOutDS = GDALDataset::ToHandle(m_outputDataset.GetDataset());
GDALDatasetH hSrcDS = GDALDataset::ToHandle(m_inputDataset.GetDataset());
auto poRetDS = GDALDataset::FromHandle(
GDALVectorTranslate(m_outputDataset.GetName().c_str(), hOutDS, 1,
&hSrcDS, psOptions, nullptr));
GDALVectorTranslateOptionsFree(psOptions);
if (!poRetDS)
return false;
if (!hOutDS)
{
m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
}
return true;
}
//! @endcond

View File

@ -0,0 +1,44 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "write" step of "vector pipeline"
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#ifndef GDALALG_VECTOR_WRITE_INCLUDED
#define GDALALG_VECTOR_WRITE_INCLUDED
#include "gdalalg_vector_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALVectorWriteAlgorithm */
/************************************************************************/
class GDALVectorWriteAlgorithm final : public GDALVectorPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "write";
static constexpr const char *DESCRIPTION = "Write a vector dataset.";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
{
return {};
}
GDALVectorWriteAlgorithm();
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
};
//! @endcond
#endif /* GDALALG_VECTOR_WRITE_INCLUDED */

View File

@ -453,6 +453,12 @@ struct GDALVectorTranslateOptions
/*! Whether to unset geometry coordinate precision */
bool bUnsetCoordPrecision = false;
/*! set to true to prevent overwriting existing dataset */
bool bNoOverwrite = false;
/*! set to true to prevent if called from "gdal vector convert" */
bool bInvokedFromGdalVectorConvert = false;
};
struct TargetLayerInfo
@ -2514,7 +2520,17 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS,
bError = strcmp(psOptions->osNewLayerName.c_str(),
psOptions->aosLayers[0]) == 0;
else if (psOptions->osSQLStatement.empty())
bError = true;
{
if (psOptions->aosLayers.empty() && poDS->GetLayerCount() == 1)
{
bError = strcmp(psOptions->osNewLayerName.c_str(),
poDS->GetLayer(0)->GetName()) == 0;
}
else
{
bError = true;
}
}
if (bError)
{
CPLError(CE_Failure, CPLE_IllegalArg,
@ -2607,6 +2623,27 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS,
{
GDALDriverManager *poDM = GetGDALDriverManager();
if (psOptions->bNoOverwrite && !EQUAL(pszDest, ""))
{
VSIStatBufL sStat;
if (VSIStatL(pszDest, &sStat) == 0)
{
CPLError(CE_Failure, CPLE_AppDefined,
"File '%s' already exists. Specify the --overwrite "
"option to overwrite it.",
pszDest);
return nullptr;
}
else if (std::unique_ptr<GDALDataset>(GDALDataset::Open(pszDest)))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Dataset '%s' already exists. Specify the --overwrite "
"option to overwrite it.",
pszDest);
return nullptr;
}
}
if (psOptions->osFormat.empty())
{
if (aoDrivers.empty())
@ -4896,10 +4933,20 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName,
/* -------------------------------------------------------------------- */
else if (!bAppend && !m_bNewDataSource)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Layer %s already exists, and -append not specified.\n"
" Consider using -append, or -overwrite.",
pszNewLayerName);
if (psOptions->bInvokedFromGdalVectorConvert)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Layer %s already exists, and --append not specified. "
"Consider using --append, or --overwrite-layer.",
pszNewLayerName);
}
else
{
CPLError(CE_Failure, CPLE_AppDefined,
"Layer %s already exists, and -append not specified.\n"
" Consider using -append, or -overwrite.",
pszNewLayerName);
}
return nullptr;
}
else
@ -7935,6 +7982,16 @@ static std::unique_ptr<GDALArgumentParser> GDALVectorTranslateOptionsGetParser(
.help(_("Disable copying of metadata from source dataset and layers "
"into target dataset and layers."));
// Undocumented option used by gdal vector convert
argParser->add_argument("--no-overwrite")
.store_into(psOptions->bNoOverwrite)
.hidden();
// Undocumented option used by gdal vector convert
argParser->add_argument("--invoked-from-gdal-vector-convert")
.store_into(psOptions->bInvokedFromGdalVectorConvert)
.hidden();
if (psOptionsForBinary)
{
argParser->add_argument("dst_dataset_name")

View File

@ -2423,6 +2423,12 @@ static std::unique_ptr<GDALArgumentParser> GDALVectorInfoOptionsGetParser(
})
.help(_("Format/driver name(s) to try when opening the input file."));
argParser->add_argument("-stdout")
.flag()
.store_into(psOptions->bStdoutOutput)
.hidden()
.help(_("Directly output on stdout (format=text mode only)"));
auto &argFilename = argParser->add_argument("filename")
.action(
[psOptionsForBinary](const std::string &s)

View File

@ -75,6 +75,7 @@ add_executable(
test_cpl.cpp
test_gdal.cpp
test_gdal_aaigrid.cpp
test_gdal_algorithm.cpp
test_gdal_dted.cpp
test_gdal_gtiff.cpp
test_gdal_minmax_element.cpp

File diff suppressed because it is too large Load Diff

184
autotest/gcore/algorithm.py Executable file
View File

@ -0,0 +1,184 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: Test gdalalgorithm.h functionality
# Author: Even Rouault <even dot rouault at spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import json
import pytest
from osgeo import gdal
def test_algorithm(tmp_path):
reg = gdal.GetGlobalAlgorithmRegistry()
names = reg.GetAlgNames()
assert isinstance(names, list)
assert "raster" in names
alg = reg.InstantiateAlg("non_existing")
assert alg is None
with pytest.raises(Exception, match="NULL"):
reg.InstantiateAlg(None)
alg = reg.InstantiateAlg("raster")
assert alg
assert alg.GetName() == "raster"
assert alg.GetDescription() == "Raster commands."
assert alg.GetLongDescription() == ""
assert alg.GetHelpFullURL() == ""
assert alg.HasSubAlgorithms()
with pytest.raises(Exception, match="NULL"):
alg.InstantiateSubAlgorithm(None)
assert alg.InstantiateSubAlgorithm("non_existing") is None
subalgs = alg.GetSubAlgorithmNames()
assert isinstance(subalgs, list)
assert "info" in subalgs
convert = alg.InstantiateSubAlgorithm("convert")
assert convert
assert json.loads(convert.GetUsageAsJSON())["name"] == "convert"
outfilename = str(tmp_path / "out.tif")
assert convert.ParseCommandLineArguments(["data/byte.tif", outfilename])
last_pct = [0]
def my_progress(pct, msg, user_data):
last_pct[0] = pct
return True
assert convert.Run(my_progress)
assert last_pct[0] == 1.0
assert convert.Finalize()
with gdal.Open(outfilename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
gdal.Unlink(outfilename)
last_pct = [0]
convert = alg.InstantiateSubAlgorithm("convert")
assert convert.ParseRunAndFinalize(["data/byte.tif", outfilename], my_progress)
assert last_pct[0] == 1.0
assert convert.Finalize()
with gdal.Open(outfilename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
argNames = convert.GetArgNames()
assert isinstance(argNames, list)
assert "input" in argNames
with pytest.raises(Exception):
convert.GetArg(None)
assert convert.GetArg("non_existing") is None
arg = convert.GetArg("append")
assert arg.GetName() == "append"
assert arg.GetType() == gdal.GAAT_BOOLEAN
assert arg.GetShortName() == ""
assert arg.GetDescription() == "Append as a subdataset to existing output"
assert arg.GetAliases() is None
assert arg.GetMetaVar() == ""
assert arg.GetCategory() == "Base"
assert not arg.IsPositional()
assert not arg.IsRequired()
assert arg.GetMinCount() == 0
assert arg.GetMaxCount() == 1
assert arg.GetPackedValuesAllowed()
assert arg.GetRepeatedArgAllowed()
assert arg.GetChoices() is None
assert not arg.IsExplicitlySet()
assert arg.HasDefaultValue()
assert not arg.IsHiddenForCLI()
assert not arg.IsOnlyForCLI()
assert arg.IsInput()
assert not arg.IsOutput()
assert arg.GetMutualExclusionGroup() == "overwrite-append"
assert arg.GetAsBoolean() is False
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsInteger()
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsDouble()
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsString()
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsDatasetValue()
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsIntegerList()
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsDoubleList()
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsStringList()
assert arg.Get() is False
arg = convert.GetArg("input")
with pytest.raises(Exception, match="must only be called on arguments of type"):
arg.GetAsBoolean()
###############################################################################
# Test running an algorithm without using the ParseCommandLineArguments()
# interface, but directly setting argument values.
def test_algorithm_dataset_value(tmp_path):
reg = gdal.GetGlobalAlgorithmRegistry()
raster = reg.InstantiateAlg("raster")
convert = raster.InstantiateSubAlgorithm("convert")
input_arg = convert.GetArg("input")
input_arg_value = input_arg.Get()
input_arg_value.SetName("data/byte.tif")
assert input_arg_value.GetName() == "data/byte.tif"
assert input_arg_value.GetDataset() is None
output_arg = convert.GetArg("output")
outfilename = str(tmp_path / "out.tif")
output_arg_value = output_arg.Get()
output_arg_value.SetName(outfilename)
assert convert.Run()
in_ds = input_arg_value.GetDataset()
assert in_ds is not None
assert input_arg_value.IsDatasetOwned()
out_ds = output_arg_value.GetDataset()
assert out_ds is not None
assert output_arg_value.IsDatasetOwned()
assert out_ds.GetRasterBand(1).Checksum() == 4672
output_arg_value.SetDatasetWithoutOwnership(None)
output_arg_value.SetDatasetWithOwnership(None)
with pytest.raises(
Exception,
match="Dataset object 'output' is created by algorithm and cannot be set as an input",
):
output_arg.SetDatasetWithoutOwnership(None)
with pytest.raises(
Exception,
match="Dataset object 'output' is created by algorithm and cannot be set as an input",
):
output_arg.SetDatasetWithOwnership(None)

View File

@ -105,5 +105,10 @@ for i in $prefix/include/*.h; do
${CXX} -Wall -Wpedantic -std=c++11 -c $(${GDAL_CONFIG} --cflags) $i;
done
echo "Test that we can compile all headers with C++17 using ${CXX}"
for i in $prefix/include/*.h; do
${CXX} -Wall -Wpedantic -std=c++17 -c $(${GDAL_CONFIG} --cflags) $i;
done
echo "$ERRORS tests failed out of $NTESTS"
exit $ERRORS

View File

@ -35,7 +35,7 @@ def get_cli_utility_path_internal(cli_utility_name):
if sys.platform == "win32":
cli_utility_path = cli_utility_path.replace("\\", "/")
if os.path.isfile(cli_utility_path):
ret = gdaltest.runexternal(cli_utility_path + " --utility_version")
ret = gdaltest.runexternal(cli_utility_path + " --version")
if "GDAL" in ret:
return cli_utility_path
@ -46,7 +46,7 @@ def get_cli_utility_path_internal(cli_utility_name):
print(f"Could not find {cli_utility_name} in {build_dir}/apps. Trying with PATH")
try:
cli_utility_path = cli_utility_name
ret = gdaltest.runexternal(cli_utility_path + " --utility_version")
ret = gdaltest.runexternal(cli_utility_path + " --version")
if "GDAL" in ret:
return cli_utility_path
@ -72,6 +72,14 @@ def get_cli_utility_path(cli_utility_name):
#
def get_gdal_path():
return get_cli_utility_path("gdal")
###############################################################################
#
def get_gdalinfo_path():
return get_cli_utility_path("gdalinfo")

86
autotest/utilities/test_gdal.py Executable file
View File

@ -0,0 +1,86 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal' program testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import json
import gdaltest
import pytest
import test_cli_utilities
pytestmark = pytest.mark.skipif(
test_cli_utilities.get_gdal_path() is None, reason="gdal binary not available"
)
@pytest.fixture()
def gdal_path():
return test_cli_utilities.get_gdal_path()
def test_gdal_no_argument(gdal_path):
out, err = gdaltest.runexternal_out_and_err(f"{gdal_path}")
assert out == ""
assert "gdal: Missing command name" in err
assert "Usage: " in err
assert "ret code = 1" in err
def test_gdal_help(gdal_path):
out, err = gdaltest.runexternal_out_and_err(f"{gdal_path} --help")
assert out.startswith("Usage: ")
assert err == ""
def test_gdal_json_usage(gdal_path):
out, err = gdaltest.runexternal_out_and_err(f"{gdal_path} --json-usage")
assert out.startswith("{")
assert err == ""
assert "description" in json.loads(out)
def test_gdal_invalid_command_line(gdal_path):
out, err = gdaltest.runexternal_out_and_err(f"{gdal_path} --invalid")
assert out == ""
assert "Long name option '--invalid' is unknown" in err
assert "Usage: " in err
assert "ret code = 1" in err
def test_gdal_failure_during_run(gdal_path):
out, err = gdaltest.runexternal_out_and_err(
f"{gdal_path} raster info i_do_not_exist"
)
assert out == ""
assert "i_do_not_exist:" in err
assert "Usage: " in err
assert "ret code = 1" in err
def test_gdal_success(gdal_path):
out, err = gdaltest.runexternal_out_and_err(f"{gdal_path} data/utmsmall.tif")
assert err == ""
assert "description" in json.loads(out)
def test_gdal_failure_during_finalize(gdal_path):
out, err = gdaltest.runexternal_out_and_err(
f"{gdal_path} raster convert ../gcore/data/byte.tif /vsimem/out.tif||maxlength=100"
)
assert "ret code = 1" in err

View File

@ -0,0 +1,52 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal convert' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import pytest
from osgeo import gdal
def get_convert_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
return reg.InstantiateAlg("convert")
def test_gdalalg_convert_raster(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(["data/utmsmall.tif", out_filename])
with gdal.Open(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 50054
@pytest.mark.require_driver("GPKG")
def test_gdalalg_convert_vector(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(["../ogr/data/poly.shp", out_filename])
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayer(0).GetFeatureCount() == 10
def test_gdalalg_convert_on_raster_invalid_arg():
convert = get_convert_alg()
with pytest.raises(Exception, match="Long name option '--invalid' is unknown"):
assert convert.ParseRunAndFinalize(
["--of=MEM", "--invalid", "data/utmsmall.tif", "out"]
)

View File

@ -0,0 +1,113 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal info' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import pytest
from osgeo import gdal
def get_info_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
return reg.InstantiateAlg("info")
@pytest.mark.parametrize(
"args",
[
(["--format=text", "data/utmsmall.tif"]),
(["--format=text", "data/utmsmall.tif", "--checksum"]),
],
)
def test_gdalalg_info_on_raster(args):
info = get_info_alg()
assert info.ParseRunAndFinalize(args)
output_string = info.GetActualAlgorithm().GetArg("output-string").Get()
assert output_string.startswith("Driver: GTiff/GeoTIFF")
def test_gdalalg_info_on_raster_invalid_arg():
info = get_info_alg()
with pytest.raises(Exception, match="Long name option '--invalid' is unknown"):
assert info.ParseRunAndFinalize(
["--format=text", "--invalid", "data/utmsmall.tif"]
)
@pytest.mark.parametrize(
"args",
[
(["--format=text", "data/path.shp"]),
(["data/path.shp", "--format=text"]),
(["--format=text", "-i", "data/path.shp"]),
(["--format=text", "--input", "data/path.shp"]),
(["--format=text", "--input=data/path.shp"]),
(["--format=text", "--dialect=OGRSQL", "data/path.shp"]),
],
)
def test_gdalalg_info_on_vector(args):
info = get_info_alg()
assert info.ParseRunAndFinalize(args)
output_string = info.GetActualAlgorithm().GetArg("output-string").Get()
assert output_string.startswith("INFO: Open of")
def test_gdalalg_info_on_vector_invalid_arg():
info = get_info_alg()
with pytest.raises(Exception, match="Long name option '--invalid' is unknown"):
assert info.ParseRunAndFinalize(["--format=text", "--invalid", "data/path.shp"])
def test_gdalalg_info_invalid_arg():
info = get_info_alg()
with pytest.raises(Exception, match="Long name option '--invalid' is unknown"):
assert info.ParseRunAndFinalize(["--invalid"])
def test_gdalalg_info_run_cannot_be_run():
info = get_info_alg()
ds = gdal.GetDriverByName("MEM").Create("", 1, 1)
info.GetArg("input").SetDatasetWithoutOwnership(ds)
with pytest.raises(Exception, match="method should not be called directly"):
info.Run()
@pytest.mark.require_driver("GPKG")
def test_gdalalg_info_mixed_raster_vector(tmp_vsimem):
filename = str(tmp_vsimem / "test.gpkg")
gdal.Translate(filename, "data/utmsmall.tif")
with gdal.OpenEx(filename, gdal.OF_UPDATE) as ds:
ds.CreateLayer("vector_layer")
info = get_info_alg()
with pytest.raises(Exception, match="has both raster and vector content"):
assert info.ParseRunAndFinalize([filename])
@pytest.mark.require_driver("GPKG")
def test_gdalalg_info_mixed_raster_vector_with_invalid_arg(tmp_vsimem):
filename = str(tmp_vsimem / "test.gpkg")
gdal.Translate(filename, "data/utmsmall.tif")
with gdal.OpenEx(filename, gdal.OF_UPDATE) as ds:
ds.CreateLayer("vector_layer")
info = get_info_alg()
with pytest.raises(Exception, match="has both raster and vector content"):
assert info.ParseRunAndFinalize([filename, "--invalid"])
def test_gdalalg_info_mixed_run_without_arg(tmp_vsimem):
info = get_info_alg()
info.GetArg("input").Get().SetName("data/utmsmall.tif")
with pytest.raises(Exception, match="should not be called directly"):
assert info.Run()

View File

@ -0,0 +1,27 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal raster' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import pytest
from osgeo import gdal
def get_raster_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
return reg.InstantiateAlg("raster")
def test_gdalalg_raster_run_error():
info = get_raster_alg()
with pytest.raises(Exception, match="method should not be called directly"):
info.Run()

View File

@ -0,0 +1,92 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal raster convert' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import pytest
from osgeo import gdal
def get_convert_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
raster = reg.InstantiateAlg("raster")
return raster.InstantiateSubAlgorithm("convert")
def test_gdalalg_raster_convert(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(["data/utmsmall.tif", out_filename])
with gdal.Open(out_filename, gdal.GA_Update) as ds:
assert ds.GetRasterBand(1).Checksum() == 50054
ds.GetRasterBand(1).Fill(0)
convert = get_convert_alg()
with pytest.raises(
Exception, match="already exists. Specify the --overwrite option"
):
convert.ParseRunAndFinalize(["data/utmsmall.tif", out_filename])
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["--overwrite", "--co=TILED=YES", "data/utmsmall.tif", out_filename]
)
with gdal.Open(out_filename) as ds:
assert ds.GetRasterBand(1).GetBlockSize() == [256, 256]
assert ds.GetRasterBand(1).Checksum() == 50054
def test_gdalalg_raster_convert_to_mem():
convert = get_convert_alg()
assert convert.ParseCommandLineArguments(["--of=MEM", "data/utmsmall.tif", ""])
assert convert.Run()
out_ds = convert.GetArg("output").Get().GetDataset()
assert out_ds.GetRasterBand(1).Checksum() == 50054
@pytest.mark.require_driver("GPKG")
def test_gdalalg_raster_convert_append(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(["data/utmsmall.tif", out_filename])
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
[
"data/utmsmall.tif",
out_filename,
"--append",
"--co",
"RASTER_TABLE=new_table",
]
)
with gdal.Open(out_filename) as ds:
assert len(ds.GetSubDatasets()) == 2
def test_gdalalg_raster_convert_error_output_already_set():
convert = get_convert_alg()
ds = gdal.GetDriverByName("MEM").Create("", 1, 1)
convert.GetArg("output").Get().SetDatasetWithoutOwnership(ds)
assert convert.ParseCommandLineArguments(["data/utmsmall.tif"])
with pytest.raises(
Exception,
match="gdal raster convert does not support outputting to an already opened output dataset",
):
convert.Run()

View File

@ -0,0 +1,157 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal raster info' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import json
import pytest
from osgeo import gdal
def get_info_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
raster = reg.InstantiateAlg("raster")
return raster.InstantiateSubAlgorithm("info")
def test_gdalalg_raster_info_stdout():
import gdaltest
import test_cli_utilities
gdal_path = test_cli_utilities.get_gdal_path()
if gdal_path is None:
pytest.skip("gdal binary missing")
out, err = gdaltest.runexternal_out_and_err(
f"{gdal_path} raster info data/utmsmall.tif"
)
j = json.loads(out)
assert len(j["bands"]) == 1
def test_gdalalg_raster_info():
info = get_info_alg()
assert info.ParseRunAndFinalize(["--format=text", "data/utmsmall.tif"])
output_string = info.GetArg("output-string").Get()
assert output_string.startswith("Driver: GTiff/GeoTIFF")
def test_gdalalg_raster_info_mm_checksum():
info = get_info_alg()
assert info.ParseRunAndFinalize(
["--format=text", "--mm", "--checksum", "data/utmsmall.tif"]
)
output_string = info.GetArg("output-string").Get()
assert " Computed Min/Max=0.000,255.000" in output_string
assert "Checksum=" in output_string
def test_gdalalg_raster_info_stats():
info = get_info_alg()
ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
assert info.ParseRunAndFinalize(["--stats"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert "stdDev" in j["bands"][0]
def test_gdalalg_raster_info_approx_stats():
info = get_info_alg()
ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
assert info.ParseRunAndFinalize(["--approx-stats"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert "stdDev" in j["bands"][0]
def test_gdalalg_raster_info_hist():
info = get_info_alg()
ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
assert info.ParseRunAndFinalize(["--hist"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert "histogram" in j["bands"][0]
def test_gdalalg_raster_info_no_options():
info = get_info_alg()
ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
assert info.ParseRunAndFinalize(
["--no-gcp", "--no-md", "--no-ct", "--no-fl", "--no-nodata", "--no-mask"]
)
def test_gdalalg_raster_info_list_mdd():
info = get_info_alg()
ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM")
ds.SetMetadataItem("foo", "bar", "MY_DOMAIN")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
assert info.ParseRunAndFinalize(["--list-mdd"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert "MY_DOMAIN" in j["metadata"]["metadataDomains"]
def test_gdalalg_raster_info_mdd_all():
info = get_info_alg()
ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM")
ds.SetMetadataItem("foo", "bar", "MY_DOMAIN")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
assert info.ParseRunAndFinalize(["--mdd=all"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert j["metadata"] == {
"": {"AREA_OR_POINT": "Area"},
"MY_DOMAIN": {"foo": "bar"},
"DERIVED_SUBDATASETS": {
"DERIVED_SUBDATASET_1_NAME": "DERIVED_SUBDATASET:LOGAMPLITUDE:",
"DERIVED_SUBDATASET_1_DESC": "log10 of amplitude of input bands from ",
},
}
def test_gdalalg_raster_info_list_subdataset():
info = get_info_alg()
assert info.ParseRunAndFinalize(
["--input=../gcore/data/tiff_with_subifds.tif", "--subdataset=2"]
)
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert j["description"] == "GTIFF_DIR:2:../gcore/data/tiff_with_subifds.tif"
def test_gdalalg_raster_info_list_subdataset_error():
info = get_info_alg()
with pytest.raises(
Exception,
match="Invalid value for 'subdataset' argument. Should be between 1 and 2",
):
info.ParseRunAndFinalize(
["--input=../gcore/data/tiff_with_subifds.tif", "--subdataset=3"]
)
def test_gdalalg_raster_info_list_subdataset_error_cannot_open_subdataset():
info = get_info_alg()
ds = gdal.GetDriverByName("MEM").Create("", 1, 1)
ds.SetMetadataItem("SUBDATASET_1_DESC", "desc", "SUBDATASETS")
ds.SetMetadataItem("SUBDATASET_1_NAME", "i_do_not_exist", "SUBDATASETS")
info.GetArg("input").SetDatasetWithoutOwnership(ds)
with pytest.raises(
Exception,
match="i_do_not_exist",
):
info.ParseRunAndFinalize(["--subdataset=1"])

View File

@ -0,0 +1,27 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal vector' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import pytest
from osgeo import gdal
def get_vector_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
return reg.InstantiateAlg("vector")
def test_gdalalg_vector_run_error():
info = get_vector_alg()
with pytest.raises(Exception, match="method should not be called directly"):
info.Run()

View File

@ -0,0 +1,165 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal vector convert' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import pytest
from osgeo import gdal
def get_convert_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
vector = reg.InstantiateAlg("vector")
return vector.InstantiateSubAlgorithm("convert")
@pytest.mark.require_driver("GPKG")
def test_gdalalg_vector_convert_base(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(["../ogr/data/poly.shp", out_filename])
with gdal.OpenEx(out_filename, gdal.OF_UPDATE) as ds:
assert ds.GetLayer(0).GetFeatureCount() == 10
for i in range(10):
ds.GetLayer(0).DeleteFeature(i + 1)
convert = get_convert_alg()
with pytest.raises(
Exception, match="already exists. Specify the --overwrite option"
):
convert.ParseRunAndFinalize(["../ogr/data/poly.shp", out_filename])
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayer(0).GetFeatureCount() == 0
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["--overwrite", "../ogr/data/poly.shp", out_filename]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayer(0).GetFeatureCount() == 10
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["--append", "../ogr/data/poly.shp", out_filename]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayer(0).GetFeatureCount() == 20
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["--update", "--nln", "layer2", "../ogr/data/poly.shp", out_filename]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayerByName("poly").GetFeatureCount() == 20
assert ds.GetLayerByName("layer2").GetFeatureCount() == 10
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
[
"--of",
"GPKG",
"--overwrite-layer",
"--nln",
"poly",
"../ogr/data/poly.shp",
out_filename,
]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayerByName("poly").GetFeatureCount() == 10
assert ds.GetLayerByName("layer2").GetFeatureCount() == 10
@pytest.mark.require_driver("GPKG")
def test_gdalalg_vector_convert_dsco(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["../ogr/data/poly.shp", out_filename, "--co", "ADD_GPKG_OGR_CONTENTS=NO"]
)
with gdal.OpenEx(out_filename) as ds:
with ds.ExecuteSQL(
"SELECT * FROM sqlite_master WHERE name = 'gpkg_ogr_contents'"
) as lyr:
assert lyr.GetFeatureCount() == 0
@pytest.mark.require_driver("GPKG")
def test_gdalalg_vector_convert_lco(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["../ogr/data/poly.shp", out_filename, "--lco", "FID=my_fid"]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayer(0).GetFIDColumn() == "my_fid"
def test_gdalalg_vector_convert_progress(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.shp")
last_pct = [0]
def my_progress(pct, msg, user_data):
last_pct[0] = pct
return True
convert = get_convert_alg()
assert convert.ParseRunAndFinalize(
["../ogr/data/poly.shp", out_filename], my_progress
)
assert last_pct[0] == 1.0
with gdal.OpenEx(out_filename) as ds:
assert ds.GetLayer(0).GetFeatureCount() == 10
def test_gdalalg_vector_wrong_layer_name(tmp_vsimem):
convert = get_convert_alg()
with pytest.raises(Exception, match="Couldn't fetch requested layer"):
convert.ParseRunAndFinalize(
[
"../ogr/data/poly.shp",
"--of=Memory",
"--output=empty",
"--layer",
"invalid",
]
)
def test_gdalalg_vector_convert_error_output_not_set():
convert = get_convert_alg()
convert.GetArg("input").Get().SetName("../ogr/data/poly.shp")
convert.GetArg("output").Set(convert.GetArg("output").Get())
with pytest.raises(
Exception,
match="convert: Argument 'output' has no dataset object or dataset name",
):
convert.Run()

View File

@ -0,0 +1,115 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal vector info' testing
# Author: Even Rouault <even dot rouault @ spatialys.com>
#
###############################################################################
# Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################
import json
import pytest
from osgeo import gdal
def get_info_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
vector = reg.InstantiateAlg("vector")
return vector.InstantiateSubAlgorithm("info")
def test_gdalalg_vector_info_stdout():
import gdaltest
import test_cli_utilities
gdal_path = test_cli_utilities.get_gdal_path()
if gdal_path is None:
pytest.skip("gdal binary missing")
out, err = gdaltest.runexternal_out_and_err(
f"{gdal_path} vector info data/path.shp"
)
j = json.loads(out)
assert j["layers"][0]["name"] == "path"
assert "features" not in j["layers"][0]
def test_gdalalg_vector_info_text():
info = get_info_alg()
assert info.ParseRunAndFinalize(["--format=text", "data/path.shp"])
output_string = info.GetArg("output-string").Get()
assert output_string.startswith("INFO: Open of")
def test_gdalalg_vector_info_json():
info = get_info_alg()
assert info.ParseRunAndFinalize(["data/path.shp"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert j["layers"][0]["name"] == "path"
assert "features" not in j["layers"][0]
def test_gdalalg_vector_info_features():
info = get_info_alg()
assert info.ParseRunAndFinalize(["--features", "data/path.shp"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert "features" in j["layers"][0]
def test_gdalalg_vector_info_sql():
info = get_info_alg()
assert info.ParseRunAndFinalize(
["--sql", "SELECT 1 AS foo FROM path", "data/path.shp"]
)
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert len(j["layers"][0]["fields"]) == 1
assert j["layers"][0]["fields"][0]["name"] == "foo"
def test_gdalalg_vector_info_layer():
info = get_info_alg()
assert info.ParseRunAndFinalize(["-l", "path", "data/path.shp"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert j["layers"][0]["name"] == "path"
def test_gdalalg_vector_info_wrong_layer():
info = get_info_alg()
with pytest.raises(Exception, match="Couldn't fetch requested layer"):
info.ParseRunAndFinalize(["-l", "invalid", "data/path.shp"])
@pytest.mark.parametrize("cond,featureCount", [("0", 0), ("1", 1)])
def test_gdalalg_vector_info_where(cond, featureCount):
info = get_info_alg()
assert info.ParseRunAndFinalize(["--where", cond, "data/path.shp"])
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert j["layers"][0]["featureCount"] == featureCount
@pytest.mark.require_driver("SQLite")
def test_gdalalg_vector_info_dialect():
info = get_info_alg()
assert info.ParseRunAndFinalize(
[
"--features",
"--dialect",
"SQLite",
"--sql",
"SELECT sqlite_version() AS version",
"data/path.shp",
]
)
output_string = info.GetArg("output-string").Get()
j = json.loads(output_string)
assert j["layers"][0]["features"][0]["properties"]["version"].startswith("3.")

View File

@ -2,6 +2,8 @@
add_library(
gcore OBJECT
gdal_adbc.cpp
gdalalgorithm.cpp
gdalalgorithmregistry.cpp
gdalopeninfo.cpp
gdaldriver.cpp
gdaldrivermanager.cpp
@ -199,6 +201,7 @@ target_public_header(
HEADERS
${CMAKE_CURRENT_BINARY_DIR}/gdal_version_full/gdal_version.h
gdal.h
gdalalgorithm.h
gdaljp2metadata.h
gdaljp2abstractdataset.h
gdal_frmts.h

View File

@ -3786,7 +3786,8 @@ int CPL_STDCALL GDALGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv,
bool bJSON = false;
for (int i = 1; i < nArgc; i++)
{
if (strcmp(papszArgv[i], "-json") == 0)
if (strcmp(papszArgv[i], "-json") == 0 ||
strcmp(papszArgv[i], "--json") == 0)
{
bJSON = true;
break;

3986
gcore/gdalalgorithm.cpp Normal file

File diff suppressed because it is too large Load Diff

2204
gcore/gdalalgorithm.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,210 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: GDALAlgorithm class
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
****************************************************************************/
#include "gdalalgorithm.h"
#include "gdalalg_main.h"
#include "cpl_vsi.h"
#include "gdal_priv.h"
#include <cassert>
/************************************************************************/
/* GDALAlgorithmRegistry::~GDALAlgorithmRegistry() */
/************************************************************************/
GDALAlgorithmRegistry::~GDALAlgorithmRegistry() = default;
/************************************************************************/
/* GDALAlgorithmRegistry::Register() */
/************************************************************************/
bool GDALAlgorithmRegistry::Register(const GDALAlgorithmRegistry::AlgInfo &info)
{
if (cpl::contains(m_mapNameToInfo, info.m_name))
{
CPLError(CE_Failure, CPLE_AppDefined,
"GDAL algorithm '%s' already registered!",
info.m_name.c_str());
return false;
}
for (const std::string &alias : info.m_aliases)
{
if (cpl::contains(m_mapAliasToInfo, alias) ||
cpl::contains(m_mapHiddenAliasToInfo, alias))
{
CPLError(CE_Failure, CPLE_AppDefined,
"An algorithm with alias '%s' is already registered!",
alias.c_str());
return false;
}
}
m_mapNameToInfo[info.m_name] = info;
bool hidden = false;
for (const std::string &alias : info.m_aliases)
{
if (alias == HIDDEN_ALIAS_SEPARATOR)
hidden = true;
else if (hidden)
m_mapAliasToInfo[alias] = info;
else
m_mapHiddenAliasToInfo[alias] = info;
}
return true;
}
/************************************************************************/
/* GDALAlgorithmRegistry::Instantiate() */
/************************************************************************/
std::unique_ptr<GDALAlgorithm>
GDALAlgorithmRegistry::Instantiate(const std::string &name) const
{
auto iter = m_mapNameToInfo.find(name);
if (iter == m_mapNameToInfo.end())
{
iter = m_mapAliasToInfo.find(name);
if (iter == m_mapAliasToInfo.end())
{
iter = m_mapHiddenAliasToInfo.find(name);
if (iter == m_mapHiddenAliasToInfo.end())
{
return nullptr;
}
}
}
auto alg = iter->second.m_creationFunc();
alg->m_aliases = iter->second.m_aliases;
return alg;
}
/************************************************************************/
/* GDALAlgorithmRegistry::GetNames() */
/************************************************************************/
std::vector<std::string> GDALAlgorithmRegistry::GetNames() const
{
std::vector<std::string> res;
for (const auto &iter : m_mapNameToInfo)
{
res.push_back(iter.first);
}
return res;
}
/************************************************************************/
/* GDALGlobalAlgorithmRegistry::GetSingleton() */
/************************************************************************/
/* static */ GDALGlobalAlgorithmRegistry &
GDALGlobalAlgorithmRegistry::GetSingleton()
{
static GDALGlobalAlgorithmRegistry singleton;
return singleton;
}
/************************************************************************/
/* GDALGlobalAlgorithmRegistry::Instantiate() */
/************************************************************************/
std::unique_ptr<GDALAlgorithm>
GDALGlobalAlgorithmRegistry::Instantiate(const std::string &name) const
{
if (name == GDALGlobalAlgorithmRegistry::ROOT_ALG_NAME)
return std::make_unique<GDALMainAlgorithm>();
return GDALAlgorithmRegistry::Instantiate(name);
}
/************************************************************************/
/* struct GDALAlgorithmRegistryHS */
/************************************************************************/
struct GDALAlgorithmRegistryHS
{
GDALAlgorithmRegistry *ptr = nullptr;
};
/************************************************************************/
/* GDALGetGlobalAlgorithmRegistry() */
/************************************************************************/
/** Gets a handle to the GDALGetGlobalAlgorithmRegistry which references
* all available top-level GDAL algorithms ("raster", "vector", etc.)
*
* The handle must be released with GDALAlgorithmRegistryRelease() (but
* this does not destroy the GDALAlgorithmRegistryRelease singleton).
*
* @since 3.11
*/
GDALAlgorithmRegistryH GDALGetGlobalAlgorithmRegistry()
{
auto ret = std::make_unique<GDALAlgorithmRegistryHS>();
ret->ptr = &(GDALGlobalAlgorithmRegistry::GetSingleton());
return ret.release();
}
/************************************************************************/
/* GDALAlgorithmRegistryRelease() */
/************************************************************************/
/** Release a handle to an algorithm registry, but this does not destroy the
* registry itself.
*
* @since 3.11
*/
void GDALAlgorithmRegistryRelease(GDALAlgorithmRegistryH hReg)
{
delete hReg;
}
/************************************************************************/
/* GDALAlgorithmRegistryGetAlgNames() */
/************************************************************************/
/** Return the names of the algorithms registered in the registry passed as
* parameter.
*
* @param hReg Handle to a registry. Must NOT be null.
* @return a NULL terminated list of names, which must be destroyed with
* CSLDestroy()
*
* @since 3.11
*/
char **GDALAlgorithmRegistryGetAlgNames(GDALAlgorithmRegistryH hReg)
{
VALIDATE_POINTER1(hReg, __func__, nullptr);
return CPLStringList(hReg->ptr->GetNames()).StealList();
}
/************************************************************************/
/* GDALAlgorithmRegistryInstantiateAlg() */
/************************************************************************/
/** Instantiate an algorithm available in a registry from its name.
*
* @param hReg Handle to a registry. Must NOT be null.
* @param pszAlgName Algorithm name. Must NOT be null.
* @return an handle to the algorithm (to be freed with GDALAlgorithmRelease),
* or NULL if the algorithm does not exist or another error occurred.
*
* @since 3.11
*/
GDALAlgorithmH GDALAlgorithmRegistryInstantiateAlg(GDALAlgorithmRegistryH hReg,
const char *pszAlgName)
{
VALIDATE_POINTER1(hReg, __func__, nullptr);
VALIDATE_POINTER1(pszAlgName, __func__, nullptr);
auto alg = hReg->ptr->Instantiate(pszAlgName);
return alg ? std::make_unique<GDALAlgorithmHS>(std::move(alg)).release()
: nullptr;
}

View File

@ -1,6 +1,7 @@
# CMake4GDAL project is distributed under MIT license. See accompanying file LICENSE.txt.
set(GDAL_SWIG_COMMON_INTERFACE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/include/Algorithm.i
${CMAKE_CURRENT_SOURCE_DIR}/include/Band.i
${CMAKE_CURRENT_SOURCE_DIR}/include/ColorTable.i
${CMAKE_CURRENT_SOURCE_DIR}/include/cpl.i

416
swig/include/Algorithm.i Normal file
View File

@ -0,0 +1,416 @@
/******************************************************************************
* Name: Algorithm.i
* Project: GDAL Python Interface
* Purpose: GDAL Algorithm SWIG Interface declarations.
* Author: Even Rouault <even dot rouault at spatialys.com>
*
******************************************************************************
* Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
*
* SPDX-License-Identifier: MIT
*****************************************************************************/
%{
#include "gdalalgorithm.h"
typedef GDALAlgorithmRegistryHS GDALAlgorithmRegistryHS;
typedef GDALAlgorithmHS GDALAlgorithmHS;
typedef GDALAlgorithmArgHS GDALAlgorithmArgHS;
typedef GDALArgDatasetValueHS GDALArgDatasetValueHS;
%}
typedef int GDALAlgorithmArgType;
%rename (AlgorithmRegistry) GDALAlgorithmRegistryHS;
%rename (Algorithm) GDALAlgorithmHS;
%rename (AlgorithmArg) GDALAlgorithmArgHS;
%rename (ArgDatasetValue) GDALArgDatasetValueHS;
/* -------------------------------------------------------------------- */
/* Global methods */
/* -------------------------------------------------------------------- */
%rename (GetGlobalAlgorithmRegistry) GDALGetGlobalAlgorithmRegistry;
%newobject GDALGetGlobalAlgorithmRegistry;
GDALAlgorithmRegistryHS* GDALGetGlobalAlgorithmRegistry(void);
%rename (AlgorithmArgTypeIsList) GDALAlgorithmArgTypeIsList;
bool GDALAlgorithmArgTypeIsList(GDALAlgorithmArgType type);
%rename (AlgorithmArgTypeName) GDALAlgorithmArgTypeName;
const char *GDALAlgorithmArgTypeName(GDALAlgorithmArgType type);
/* -------------------------------------------------------------------- */
/* GDALAlgorithmArgHS */
/* -------------------------------------------------------------------- */
class GDALAlgorithmArgHS {
private:
GDALAlgorithmArgHS();
public:
%extend {
~GDALAlgorithmArgHS() {
GDALAlgorithmArgRelease( self );
}
const char* GetName() {
return GDALAlgorithmArgGetName(self);
}
#if defined(SWIGCSHARP)
GDALAlgorithmArgType GetType_()
#else
GDALAlgorithmArgType GetType()
#endif
{
return GDALAlgorithmArgGetType(self);
}
const char* GetDescription() {
return GDALAlgorithmArgGetDescription(self);
}
const char* GetShortName() {
return GDALAlgorithmArgGetShortName(self);
}
%apply (char **CSL) {char **};
char **GetAliases() {
return GDALAlgorithmArgGetAliases( self );
}
%clear char **;
const char* GetMetaVar() {
return GDALAlgorithmArgGetMetaVar(self);
}
const char* GetCategory() {
return GDALAlgorithmArgGetCategory(self);
}
bool IsPositional() {
return GDALAlgorithmArgIsPositional(self);
}
bool IsRequired() {
return GDALAlgorithmArgIsRequired(self);
}
int GetMinCount() {
return GDALAlgorithmArgGetMinCount(self);
}
int GetMaxCount() {
return GDALAlgorithmArgGetMaxCount(self);
}
bool GetPackedValuesAllowed() {
return GDALAlgorithmArgGetPackedValuesAllowed(self);
}
bool GetRepeatedArgAllowed() {
return GDALAlgorithmArgGetRepeatedArgAllowed(self);
}
%apply (char **CSL) {char **};
char **GetChoices() {
return GDALAlgorithmArgGetChoices( self );
}
%clear char **;
bool IsExplicitlySet() {
return GDALAlgorithmArgIsExplicitlySet(self);
}
bool HasDefaultValue() {
return GDALAlgorithmArgHasDefaultValue(self);
}
bool IsHiddenForCLI() {
return GDALAlgorithmArgIsHiddenForCLI(self);
}
bool IsOnlyForCLI() {
return GDALAlgorithmArgIsOnlyForCLI(self);
}
bool IsInput() {
return GDALAlgorithmArgIsInput(self);
}
bool IsOutput() {
return GDALAlgorithmArgIsOutput(self);
}
const char* GetMutualExclusionGroup() {
return GDALAlgorithmArgGetMutualExclusionGroup(self);
}
bool GetAsBoolean() {
return GDALAlgorithmArgGetAsBoolean(self);
}
const char* GetAsString() {
return GDALAlgorithmArgGetAsString(self);
}
int GetAsInteger() {
return GDALAlgorithmArgGetAsInteger(self);
}
int GetAsDouble() {
return GDALAlgorithmArgGetAsDouble(self);
}
%newobject GetAsDataset;
GDALArgDatasetValueHS* GetAsDatasetValue() {
return GDALAlgorithmArgGetAsDatasetValue(self);
}
%apply (char **CSL) {char **};
char **GetAsStringList() {
return GDALAlgorithmArgGetAsStringList( self );
}
%clear char **;
#if defined(SWIGPYTHON)
void GetAsIntegerList(int *nLen, const int **pList) {
size_t nLenSizet = 0;
*pList = GDALAlgorithmArgGetAsIntegerList(self, &nLenSizet);
*nLen = (int)nLenSizet;
}
#endif
#if defined(SWIGPYTHON)
void GetAsDoubleList(int *nLen, const double **pList) {
size_t nLenSizet = 0;
*pList = GDALAlgorithmArgGetAsDoubleList(self, &nLenSizet);
*nLen = (int)nLenSizet;
}
#endif
bool SetAsBoolean(bool value) {
return GDALAlgorithmArgSetAsBoolean(self, value);
}
bool SetAsString(const char* value) {
return GDALAlgorithmArgSetAsString(self, value);
}
bool SetAsInteger(int value) {
return GDALAlgorithmArgSetAsInteger(self, value);
}
bool SetAsDouble(double value) {
return GDALAlgorithmArgSetAsDouble(self, value);
}
bool SetAsDatasetValue(GDALArgDatasetValueHS* value) {
return GDALAlgorithmArgSetAsDatasetValue(self, value);
}
%apply (char **options) {char**value};
bool SetAsStringList(char** value) {
return GDALAlgorithmArgSetAsStringList(self, value);
}
%clear char** value;
bool SetAsIntegerList(int nList, int *pList) {
return GDALAlgorithmArgSetAsIntegerList(self, nList, pList);
}
bool SetAsDoubleList(int nList, double *pList) {
return GDALAlgorithmArgSetAsDoubleList(self, nList, pList);
}
void SetDatasetWithoutOwnership(GDALDatasetShadow* ds) {
GDALAlgorithmArgSetDatasetWithoutOwnership(self, ds);
}
void SetDatasetWithOwnership(GDALDatasetShadow* ds) {
GDALAlgorithmArgSetDatasetWithOwnership(self, ds);
}
}
};
/* -------------------------------------------------------------------- */
/* GDALAlgorithmHS */
/* -------------------------------------------------------------------- */
class GDALAlgorithmHS {
private:
GDALAlgorithmHS();
public:
%extend {
~GDALAlgorithmHS() {
GDALAlgorithmRelease( self );
}
const char* GetName() {
return GDALAlgorithmGetName(self);
}
const char* GetDescription() {
return GDALAlgorithmGetDescription(self);
}
const char* GetLongDescription() {
return GDALAlgorithmGetLongDescription(self);
}
const char* GetHelpFullURL() {
return GDALAlgorithmGetHelpFullURL(self);
}
bool HasSubAlgorithms() {
return GDALAlgorithmHasSubAlgorithms(self);
}
%apply (char **CSL) {char **};
char **GetSubAlgorithmNames() {
return GDALAlgorithmGetSubAlgorithmNames( self );
}
%clear char **;
%apply Pointer NONNULL {const char *algName};
%newobject InstantiateSubAlgorithm;
GDALAlgorithmHS* InstantiateSubAlgorithm(const char *algName) {
return GDALAlgorithmInstantiateSubAlgorithm(self, algName);
}
%clear const char *algName;
%apply (char **options) { char ** args };
bool ParseCommandLineArguments(char** args) {
return GDALAlgorithmParseCommandLineArguments(self, args);
}
%clear char** args;
%newobject GetActualAlgorithm;
GDALAlgorithmHS* GetActualAlgorithm() {
return GDALAlgorithmGetActualAlgorithm(self);
}
bool Run(GDALProgressFunc callback=NULL, void* callback_data=NULL) {
return GDALAlgorithmRun(self, callback, callback_data);
}
#if defined(SWIGCSHARP)
bool Finalize_()
#else
bool Finalize()
#endif
{
return GDALAlgorithmFinalize(self);
}
%apply (char **options) { char ** args };
bool ParseRunAndFinalize(char** args, GDALProgressFunc callback=NULL, void* callback_data=NULL) {
return GDALAlgorithmParseCommandLineArguments(self, args) &&
GDALAlgorithmRun(self, callback, callback_data) &&
GDALAlgorithmFinalize(self);
}
%clear char** args;
retStringAndCPLFree *GetUsageAsJSON() {
return GDALAlgorithmGetUsageAsJSON(self);
}
%apply (char **CSL) {char **};
char **GetArgNames() {
return GDALAlgorithmGetArgNames( self );
}
%clear char **;
%apply Pointer NONNULL {const char *argName};
%newobject GetArg;
GDALAlgorithmArgHS* GetArg(const char *argName) {
return GDALAlgorithmGetArg(self, argName);
}
%clear const char *argName;
}
};
/* -------------------------------------------------------------------- */
/* GDALAlgorithmRegistryHS */
/* -------------------------------------------------------------------- */
class GDALAlgorithmRegistryHS {
private:
GDALAlgorithmRegistryHS();
public:
%extend {
~GDALAlgorithmRegistryHS() {
GDALAlgorithmRegistryRelease( self );
}
%apply (char **CSL) {char **};
char **GetAlgNames() {
return GDALAlgorithmRegistryGetAlgNames( self );
}
%clear char **;
%apply Pointer NONNULL {const char *algName};
%newobject InstantiateAlg;
GDALAlgorithmHS* InstantiateAlg(const char *algName) {
return GDALAlgorithmRegistryInstantiateAlg(self, algName);
}
%clear const char *algName;
}
};
/* -------------------------------------------------------------------- */
/* GDALArgDatasetValueHS */
/* -------------------------------------------------------------------- */
class GDALArgDatasetValueHS {
private:
GDALArgDatasetValueHS();
public:
%extend {
~GDALArgDatasetValueHS() {
GDALArgDatasetValueRelease( self );
}
#if defined(SWIGCSHARP)
int GetType_()
#else
int GetType()
#endif
{
return GDALArgDatasetValueGetType(self);
}
const char* GetName() {
return GDALArgDatasetValueGetName(self);
}
GDALDatasetShadow* GetDataset() {
return GDALArgDatasetValueGetDataset(self);
}
bool IsDatasetOwned() {
return GDALArgDatasetValueIsDatasetOwned(self);
}
int GetInputFlags() {
return GDALArgDatasetValueGetInputFlags(self);
}
int GetOutputFlags() {
return GDALArgDatasetValueGetOutputFlags(self);
}
void SetName(const char* name) {
GDALArgDatasetValueSetName(self, name);
}
void SetDatasetWithoutOwnership(GDALDatasetShadow* ds) {
GDALArgDatasetValueSetDatasetWithoutOwnership(self, ds);
}
void SetDatasetWithOwnership(GDALDatasetShadow* ds) {
GDALArgDatasetValueSetDatasetWithOwnership(self, ds);
}
}
};

View File

@ -612,6 +612,8 @@ RETURN_NONE GDALGCPsToGeoTransform( int nGCPs, GDAL_GCP const * pGCPs,
//************************************************************************
%include "Operations.i"
%include "Algorithm.i"
%apply (double argin[ANY]) {(double padfGeoTransform[6])};
%apply (double *OUTPUT) {(double *pdfGeoX)};
%apply (double *OUTPUT) {(double *pdfGeoY)};

View File

@ -31,6 +31,7 @@
#include "gdalwarper.h"
#include "cpl_string.h"
#include "cpl_minixml.h"
#include "gdalalgorithm.h"
%}
// GDALDataType
@ -488,6 +489,17 @@
%constant GRT_ASSOCIATION = GRT_ASSOCIATION;
%constant GRT_AGGREGATION = GRT_AGGREGATION;
// GDALAlgorithmArgType
%constant GAAT_BOOLEAN = GAAT_BOOLEAN;
%constant GAAT_STRING = GAAT_STRING;
%constant GAAT_INTEGER = GAAT_INTEGER;
%constant GAAT_REAL = GAAT_REAL;
%constant GAAT_DATASET = GAAT_DATASET;
%constant GAAT_STRING_LIST = GAAT_STRING_LIST;
%constant GAAT_INTEGER_LIST = GAAT_INTEGER_LIST;
%constant GAAT_REAL_LIST = GAAT_REAL_LIST;
%constant GAAT_DATASET_LIST = GAAT_DATASET_LIST;
#ifdef SWIGPYTHON
%thread;
#endif

View File

@ -5152,3 +5152,53 @@ class VSIFile(BytesIO):
def tell(self):
return VSIFTellL(self._fp)
%}
/* -------------------------------------------------------------------- */
/* GDALAlgorithmArgHS */
/* -------------------------------------------------------------------- */
%extend GDALAlgorithmArgHS {
%pythoncode %{
def Get(self):
type = self.GetType()
if type == GAAT_BOOLEAN:
return self.GetAsBoolean()
if type == GAAT_STRING:
return self.GetAsString()
if type == GAAT_INTEGER:
return self.GetAsInteger()
if type == GAAT_REAL:
return self.GetAsDouble()
if type == GAAT_DATASET:
return self.GetAsDatasetValue()
if type == GAAT_STRING_LIST:
return self.GetAsStringList()
if type == GAAT_INTEGER_LIST:
return self.GetAsIntegerList()
if type == GAAT_REAL_LIST:
return self.GetAsDoubleList()
raise Exception("Unhandled algorithm argument data type")
def Set(self, value):
type = self.GetType()
if type == GAAT_BOOLEAN:
return self.SetAsBoolean(value)
if type == GAAT_STRING:
return self.SetAsString(value)
if type == GAAT_INTEGER:
return self.SetAsInteger(value)
if type == GAAT_REAL:
return self.SetAsDouble(value)
if type == GAAT_DATASET:
return self.SetAsDatasetValue(value)
if type == GAAT_STRING_LIST:
return self.SetAsStringList(value)
if type == GAAT_INTEGER_LIST:
return self.SetAsIntegerList(value)
if type == GAAT_REAL_LIST:
return self.SetAsDoubleList(value)
raise Exception("Unhandled algorithm argument data type")
%}
}