Implement initial 'gdal raster pipeline' and 'gdal raster reproject'

This commit is contained in:
Even Rouault 2024-12-05 22:58:16 +01:00
parent 93cbcce042
commit 72c8101d58
No known key found for this signature in database
GPG Key ID: 33EBBFC47B3DD87D
19 changed files with 1943 additions and 48 deletions

View File

@ -13,6 +13,10 @@ add_library(
gdalalg_raster.cpp
gdalalg_raster_info.cpp
gdalalg_raster_convert.cpp
gdalalg_raster_pipeline.cpp
gdalalg_raster_read.cpp
gdalalg_raster_reproject.cpp
gdalalg_raster_write.cpp
gdalalg_vector.cpp
gdalalg_vector_info.cpp
gdalalg_vector_convert.cpp

View File

@ -12,67 +12,24 @@
#include "cpl_error.h"
#include "gdalalgorithm.h"
//#include "gdalalg_raster_pipeline.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 *GetDatasetRef()
{
return nullptr;
}
/* cppcheck-suppress functionStatic */
void SetDataset(GDALDataset *)
{
}
private:
bool RunImpl(GDALProgressFunc, void *) override
{
return false;
}
};
/************************************************************************/
/* GDALPipelineAlgorithm */
/************************************************************************/
class GDALPipelineAlgorithm final
: public GDALDispatcherAlgorithm<GDALDummyRasterPipelineAlgorithm,
: public GDALDispatcherAlgorithm<GDALRasterPipelineAlgorithm,
GDALVectorPipelineAlgorithm>
{
public:
static constexpr const char *NAME = "pipeline";
static constexpr const char *DESCRIPTION =
"Execute a pipeline (shortcut for 'gdal vector pipeline').";
"Execute a pipeline (shortcut for 'gdal raster pipeline' or 'gdal "
"vector pipeline').";
static constexpr const char *HELP_URL = ""; // TODO
static std::vector<std::string> GetAliases()
@ -94,7 +51,7 @@ class GDALPipelineAlgorithm final
}
private:
std::unique_ptr<GDALDummyRasterPipelineAlgorithm> m_rasterPipeline{};
std::unique_ptr<GDALRasterPipelineAlgorithm> m_rasterPipeline{};
std::unique_ptr<GDALVectorPipelineAlgorithm> m_vectorPipeline{};
std::string m_format{};

View File

@ -14,6 +14,8 @@
#include "gdalalg_raster_info.h"
#include "gdalalg_raster_convert.h"
#include "gdalalg_raster_pipeline.h"
#include "gdalalg_raster_reproject.h"
/************************************************************************/
/* GDALRasterAlgorithm */
@ -35,6 +37,8 @@ class GDALRasterAlgorithm final : public GDALAlgorithm
{
RegisterSubAlgorithm<GDALRasterInfoAlgorithm>();
RegisterSubAlgorithm<GDALRasterConvertAlgorithm>();
RegisterSubAlgorithm<GDALRasterPipelineAlgorithm>();
RegisterSubAlgorithm<GDALRasterReprojectAlgorithmStandalone>();
}
private:

View File

@ -0,0 +1,553 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster 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_raster_pipeline.h"
#include "gdalalg_raster_read.h"
#include "gdalalg_raster_reproject.h"
#include "gdalalg_raster_write.h"
#include "cpl_conv.h"
#include "cpl_json.h"
#include "cpl_string.h"
#include <algorithm>
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
/************************************************************************/
GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
const std::string &name, const std::string &description,
const std::string &helpURL, bool standaloneStep)
: GDALAlgorithm(name, description, helpURL),
m_standaloneStep(standaloneStep)
{
if (m_standaloneStep)
{
AddInputArgs(false, false);
AddProgressArg();
AddOutputArgs(false);
}
}
/************************************************************************/
/* GDALRasterPipelineStepAlgorithm::AddInputArgs() */
/************************************************************************/
void GDALRasterPipelineStepAlgorithm::AddInputArgs(
bool openForMixedRasterVector, bool hiddenForCLI)
{
AddInputFormatsArg(&m_inputFormats)
.AddMetadataItem(
GAAMDI_REQUIRED_CAPABILITIES,
openForMixedRasterVector
? std::vector<std::string>{GDAL_DCAP_RASTER, GDAL_DCAP_VECTOR}
: std::vector<std::string>{GDAL_DCAP_RASTER})
.SetHiddenForCLI(hiddenForCLI);
AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
AddInputDatasetArg(&m_inputDataset,
openForMixedRasterVector
? (GDAL_OF_RASTER | GDAL_OF_VECTOR)
: GDAL_OF_RASTER,
/* positionalAndRequired = */ !hiddenForCLI);
}
/************************************************************************/
/* GDALRasterPipelineStepAlgorithm::AddOutputArgs() */
/************************************************************************/
void GDALRasterPipelineStepAlgorithm::AddOutputArgs(bool hiddenForCLI)
{
AddOutputFormatArg(&m_format)
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
{GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY})
.SetHiddenForCLI(hiddenForCLI);
AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
/* positionalAndRequired = */ !hiddenForCLI)
.SetHiddenForCLI(hiddenForCLI);
m_outputDataset.SetInputFlags(GADV_NAME | GADV_OBJECT);
AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
}
/************************************************************************/
/* GDALRasterPipelineStepAlgorithm::RunImpl() */
/************************************************************************/
bool GDALRasterPipelineStepAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
void *pProgressData)
{
if (m_standaloneStep)
{
GDALRasterReadAlgorithm readAlg;
for (auto &arg : readAlg.GetArgs())
{
auto stepArg = GetArg(arg->GetName());
if (stepArg && stepArg->IsExplicitlySet())
{
arg->SetSkipIfAlreadySet(true);
arg->SetFrom(*stepArg);
}
}
GDALRasterWriteAlgorithm writeAlg;
for (auto &arg : writeAlg.GetArgs())
{
auto stepArg = GetArg(arg->GetName());
if (stepArg && stepArg->IsExplicitlySet())
{
arg->SetSkipIfAlreadySet(true);
arg->SetFrom(*stepArg);
}
}
bool ret = false;
if (readAlg.Run())
{
m_inputDataset.Set(readAlg.m_outputDataset.GetDatasetRef());
m_outputDataset.Set(nullptr);
if (RunStep(nullptr, nullptr))
{
writeAlg.m_inputDataset.Set(m_outputDataset.GetDatasetRef());
if (writeAlg.Run(pfnProgress, pProgressData))
{
m_outputDataset.Set(
writeAlg.m_outputDataset.GetDatasetRef());
ret = true;
}
}
}
return ret;
}
else
{
return RunStep(pfnProgress, pProgressData);
}
}
/************************************************************************/
/* GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm() */
/************************************************************************/
GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
bool openForMixedRasterVector)
: GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
/*standaloneStep=*/false)
{
AddInputArgs(openForMixedRasterVector, /* hiddenForCLI = */ true);
AddProgressArg();
AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
.SetHiddenForCLI()
.SetPositional();
AddOutputArgs(/* hiddenForCLI = */ true);
m_stepRegistry.Register<GDALRasterReadAlgorithm>();
m_stepRegistry.Register<GDALRasterWriteAlgorithm>();
m_stepRegistry.Register<GDALRasterReprojectAlgorithm>();
}
/************************************************************************/
/* GDALRasterPipelineAlgorithm::GetStepAlg() */
/************************************************************************/
std::unique_ptr<GDALRasterPipelineStepAlgorithm>
GDALRasterPipelineAlgorithm::GetStepAlg(const std::string &name) const
{
auto alg = m_stepRegistry.Instantiate(name);
return std::unique_ptr<GDALRasterPipelineStepAlgorithm>(
cpl::down_cast<GDALRasterPipelineStepAlgorithm *>(alg.release()));
}
/************************************************************************/
/* GDALRasterPipelineAlgorithm::ParseCommandLineArguments() */
/************************************************************************/
bool GDALRasterPipelineAlgorithm::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 raster pipeline [--progress] "read in.tif ..."
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<GDALRasterPipelineStepAlgorithm> 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
curStep.args.push_back(arg);
}
}
// 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() != GDALRasterReadAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined, "First step should be '%s'",
GDALRasterReadAlgorithm::NAME);
return false;
}
for (size_t i = 1; i < steps.size() - 1; ++i)
{
if (steps[i].alg->GetName() == GDALRasterReadAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Only first step can be '%s'",
GDALRasterReadAlgorithm::NAME);
return false;
}
}
if (steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
GDALRasterWriteAlgorithm::NAME);
return false;
}
for (size_t i = 0; i < steps.size() - 1; ++i)
{
if (steps[i].alg->GetName() == GDALRasterWriteAlgorithm::NAME)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Only last step can be '%s'",
GDALRasterWriteAlgorithm::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;
}
/************************************************************************/
/* GDALRasterPipelineAlgorithm::RunStep() */
/************************************************************************/
bool GDALRasterPipelineAlgorithm::RunStep(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.GetDatasetRef())
{
// 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);
}
if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef())
{
// 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.GetDatasetRef();
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.GetDatasetRef())
{
m_outputDataset.Set(poCurDS);
}
return true;
}
/************************************************************************/
/* GDALAlgorithm::Finalize() */
/************************************************************************/
bool GDALRasterPipelineAlgorithm::Finalize()
{
bool ret = GDALAlgorithm::Finalize();
for (auto &step : m_steps)
{
ret = step->Finalize() && ret;
}
return ret;
}
/************************************************************************/
/* GDALRasterPipelineAlgorithm::GetUsageForCLI() */
/************************************************************************/
std::string GDALRasterPipelineAlgorithm::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 raster pipeline --progress ! read in.tif ! \\\n";
ret += " reproject --dst-crs=EPSG:32632 ! ";
ret += "write out.tif --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 = GDALRasterReadAlgorithm::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 != GDALRasterReadAlgorithm::NAME &&
name != GDALRasterWriteAlgorithm::NAME)
{
ret += '\n';
auto alg = GetStepAlg(name);
alg->SetCallPath({name});
ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
}
}
{
const auto name = GDALRasterWriteAlgorithm::NAME;
ret += '\n';
auto alg = GetStepAlg(name);
alg->SetCallPath({name});
ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
}
return ret;
}
/************************************************************************/
/* GDALRasterPipelineAlgorithm::GetUsageAsJSON() */
/************************************************************************/
std::string GDALRasterPipelineAlgorithm::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,125 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: gdal "raster 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_RASTER_PIPELINE_INCLUDED
#define GDALALG_RASTER_PIPELINE_INCLUDED
#include "gdalalgorithm.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterPipelineStepAlgorithm */
/************************************************************************/
class GDALRasterPipelineStepAlgorithm /* non final */ : public GDALAlgorithm
{
protected:
GDALRasterPipelineStepAlgorithm(const std::string &name,
const std::string &description,
const std::string &helpURL,
bool standaloneStep);
friend class GDALRasterPipelineAlgorithm;
virtual bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) = 0;
void AddInputArgs(bool openForMixedRasterVector, bool hiddenForCLI);
void AddOutputArgs(bool hiddenForCLI);
bool m_standaloneStep = false;
// 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{};
bool m_overwrite = false;
std::string m_outputLayerName{};
private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
};
/************************************************************************/
/* GDALRasterPipelineAlgorithm */
/************************************************************************/
// This is an easter egg to pay tribute to PROJ pipeline syntax
// We accept "gdal vector +gdal=pipeline +step +gdal=read +input=in.tif +step +gdal=reproject +dst-crs=EPSG:32632 +step +gdal=write +output=out.tif +overwrite"
// as an alternative to (recommended):
// "gdal vector pipeline ! read in.tif ! reproject--dst-crs=EPSG:32632 ! write out.tif --overwrite"
#ifndef GDAL_PIPELINE_PROJ_NOSTALGIA
#define GDAL_PIPELINE_PROJ_NOSTALGIA
#endif
class GDALRasterPipelineAlgorithm final : public GDALRasterPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "pipeline";
static constexpr const char *DESCRIPTION = "Process a raster dataset.";
static constexpr const char *HELP_URL =
"https://gdal.org/en/stable/programs/gdal_raster_pipeline.html";
static std::vector<std::string> GetAliases()
{
return {
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
GDALAlgorithmRegistry::HIDDEN_ALIAS_SEPARATOR,
"+pipeline",
"+gdal=pipeline",
#endif
};
}
explicit GDALRasterPipelineAlgorithm(bool openForMixedRasterVector = false);
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;
GDALDataset *GetDatasetRef()
{
return m_inputDataset.GetDatasetRef();
}
/* cppcheck-suppress functionStatic */
void SetDataset(GDALDataset *)
{
}
private:
std::string m_pipeline{};
bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::unique_ptr<GDALRasterPipelineStepAlgorithm>
GetStepAlg(const std::string &name) const;
GDALAlgorithmRegistry m_stepRegistry{};
std::vector<std::unique_ptr<GDALRasterPipelineStepAlgorithm>> m_steps{};
};
//! @endcond
#endif

View File

@ -0,0 +1,47 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "read" step of "raster 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_raster_read.h"
#include "gdal_priv.h"
#include "ogrsf_frmts.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterReadAlgorithm::GDALRasterReadAlgorithm() */
/************************************************************************/
GDALRasterReadAlgorithm::GDALRasterReadAlgorithm()
: GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
/* standaloneStep =*/false)
{
AddInputArgs(/* openForMixedRasterVector = */ false,
/* hiddenForCLI = */ false);
}
/************************************************************************/
/* GDALRasterReadAlgorithm::RunStep() */
/************************************************************************/
bool GDALRasterReadAlgorithm::RunStep(GDALProgressFunc, void *)
{
CPLAssert(m_inputDataset.GetDatasetRef());
CPLAssert(m_outputDataset.GetName().empty());
CPLAssert(!m_outputDataset.GetDatasetRef());
m_outputDataset.Set(m_inputDataset.GetDatasetRef());
return true;
}
//! @endcond

View File

@ -0,0 +1,45 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "read" step of "raster 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_RASTER_READ_INCLUDED
#define GDALALG_RASTER_READ_INCLUDED
#include "gdalalg_raster_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterReadAlgorithm */
/************************************************************************/
class GDALRasterReadAlgorithm final : public GDALRasterPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "read";
static constexpr const char *DESCRIPTION = "Read a raster dataset.";
static constexpr const char *HELP_URL =
"https://gdal.org/en/stable/programs/gdal_raster_pipeline.html";
static std::vector<std::string> GetAliases()
{
return {};
}
GDALRasterReadAlgorithm();
private:
bool RunStep(GDALProgressFunc, void *) override;
};
//! @endcond
#endif

View File

@ -0,0 +1,174 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "reproject" step of "raster 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_raster_reproject.h"
#include "gdal_priv.h"
#include "gdal_utils.h"
//! @cond Doxygen_Suppress
#ifndef _
#define _(x) (x)
#endif
/************************************************************************/
/* GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm() */
/************************************************************************/
GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep)
: GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
standaloneStep)
{
AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs).AddHiddenAlias("s_srs");
AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
.AddHiddenAlias("t_srs");
AddArg("resampling", 'r', _("Resampling method"), &m_resampling)
.SetChoices("near", "bilinear", "cubic", "cubicspline", "lanczos",
"average", "rms", "mode", "min", "max", "med", "q1", "q3",
"sum");
auto &resArg =
AddArg("resolution", 0,
_("Target resolution (in destination CRS units)"), &m_resolution)
.SetMinCount(2)
.SetMaxCount(2)
.SetRepeatedArgAllowed(false)
.SetDisplayHintAboutRepetition(false)
.SetMetaVar("<xres>,<yres>");
resArg.AddValidationAction(
[&resArg]()
{
const auto &val = resArg.Get<std::vector<double>>();
CPLAssert(val.size() == 2);
if (!(val[0] >= 0 && val[1] >= 0))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Target resolution should be strictly positive.");
return false;
}
return true;
});
auto &extentArg =
AddArg("extent", 0, _("Target extent (in destination CRS units)"),
&m_extent)
.SetMinCount(4)
.SetMaxCount(4)
.SetRepeatedArgAllowed(false)
.SetDisplayHintAboutRepetition(false)
.SetMetaVar("<xmin>,<ymin>,<xmax>,<ymax>");
extentArg.AddValidationAction(
[&extentArg]()
{
const auto &val = extentArg.Get<std::vector<double>>();
CPLAssert(val.size() == 4);
if (!(val[0] <= val[2]) || !(val[1] <= val[3]))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Value of 'extent' should be xmin,ymin,xmax,ymax with "
"xmin <= xmax and ymin <= ymax");
return false;
}
return true;
});
AddArg("target-aligned-pixels", 0,
_("Round target extent to target resolution"),
&m_targetAlignedPixels)
.AddHiddenAlias("tap");
}
/************************************************************************/
/* GDALRasterReprojectAlgorithm::RunStep() */
/************************************************************************/
bool GDALRasterReprojectAlgorithm::RunStep(GDALProgressFunc, void *)
{
CPLAssert(m_inputDataset.GetDatasetRef());
CPLAssert(m_outputDataset.GetName().empty());
CPLAssert(!m_outputDataset.GetDatasetRef());
if (!m_srsCrs.empty())
{
OGRSpatialReference oSRS;
if (oSRS.SetFromUserInput(m_srsCrs.c_str()) != OGRERR_NONE)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Invalid value for '--src-crs'");
return false;
}
}
if (!m_dstCrs.empty())
{
OGRSpatialReference oSRS;
if (oSRS.SetFromUserInput(m_dstCrs.c_str()) != OGRERR_NONE)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Invalid value for '--dst-crs'");
return false;
}
}
CPLStringList aosOptions;
aosOptions.AddString("-of");
aosOptions.AddString("VRT");
if (!m_srsCrs.empty())
{
aosOptions.AddString("-s_srs");
aosOptions.AddString(m_srsCrs.c_str());
}
if (!m_dstCrs.empty())
{
aosOptions.AddString("-t_srs");
aosOptions.AddString(m_dstCrs.c_str());
}
if (!m_resampling.empty())
{
aosOptions.AddString("-r");
aosOptions.AddString(m_resampling.c_str());
}
if (!m_resolution.empty())
{
aosOptions.AddString("-tr");
aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[0]));
aosOptions.AddString(CPLSPrintf("%.17g", m_resolution[1]));
}
if (!m_extent.empty())
{
aosOptions.AddString("-te");
aosOptions.AddString(CPLSPrintf("%.17g", m_extent[0]));
aosOptions.AddString(CPLSPrintf("%.17g", m_extent[1]));
aosOptions.AddString(CPLSPrintf("%.17g", m_extent[2]));
aosOptions.AddString(CPLSPrintf("%.17g", m_extent[3]));
}
if (m_targetAlignedPixels)
{
aosOptions.AddString("-tap");
}
GDALWarpAppOptions *psOptions =
GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
GDALDatasetH hSrcDS = GDALDataset::ToHandle(m_inputDataset.GetDatasetRef());
auto poRetDS = GDALDataset::FromHandle(
GDALWarp("", nullptr, 1, &hSrcDS, psOptions, nullptr));
GDALWarpAppOptionsFree(psOptions);
if (!poRetDS)
return false;
m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
return true;
}
//! @endcond

View File

@ -0,0 +1,67 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "reproject" step of "raster 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_RASTER_REPROJECT_INCLUDED
#define GDALALG_RASTER_REPROJECT_INCLUDED
#include "gdalalg_raster_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterReprojectAlgorithm */
/************************************************************************/
class GDALRasterReprojectAlgorithm /* non final */
: public GDALRasterPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "reproject";
static constexpr const char *DESCRIPTION = "Reproject a raster dataset.";
static constexpr const char *HELP_URL =
"https://gdal.org/en/stable/programs/gdal_raster_reproject.html";
static std::vector<std::string> GetAliases()
{
return {};
}
explicit GDALRasterReprojectAlgorithm(bool standaloneStep = false);
private:
bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
std::string m_srsCrs{};
std::string m_dstCrs{};
std::string m_resampling{};
std::vector<double> m_resolution{};
std::vector<double> m_extent{};
bool m_targetAlignedPixels = false;
};
/************************************************************************/
/* GDALRasterReprojectAlgorithmStandalone */
/************************************************************************/
class GDALRasterReprojectAlgorithmStandalone final
: public GDALRasterReprojectAlgorithm
{
public:
GDALRasterReprojectAlgorithmStandalone()
: GDALRasterReprojectAlgorithm(/* standaloneStep = */ true)
{
}
};
//! @endcond
#endif /* GDALALG_RASTER_REPROJECT_INCLUDED */

View File

@ -0,0 +1,74 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "write" step of "raster 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_raster_write.h"
#include "cpl_string.h"
#include "gdal_utils.h"
#include "gdal_priv.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterWriteAlgorithm::GDALRasterWriteAlgorithm() */
/************************************************************************/
GDALRasterWriteAlgorithm::GDALRasterWriteAlgorithm()
: GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
/* standaloneStep =*/false)
{
AddOutputArgs(/* hiddenForCLI = */ false);
}
/************************************************************************/
/* GDALRasterWriteAlgorithm::RunStep() */
/************************************************************************/
bool GDALRasterWriteAlgorithm::RunStep(GDALProgressFunc pfnProgress,
void *pProgressData)
{
CPLAssert(m_inputDataset.GetDatasetRef());
CPLAssert(!m_outputDataset.GetDatasetRef());
CPLStringList aosOptions;
if (!m_overwrite)
{
aosOptions.AddString("--no-overwrite");
}
if (!m_format.empty())
{
aosOptions.AddString("-of");
aosOptions.AddString(m_format.c_str());
}
for (const auto &co : m_creationOptions)
{
aosOptions.AddString("-co");
aosOptions.AddString(co.c_str());
}
GDALTranslateOptions *psOptions =
GDALTranslateOptionsNew(aosOptions.List(), nullptr);
GDALTranslateOptionsSetProgress(psOptions, pfnProgress, pProgressData);
GDALDatasetH hSrcDS = GDALDataset::ToHandle(m_inputDataset.GetDatasetRef());
auto poRetDS = GDALDataset::FromHandle(GDALTranslate(
m_outputDataset.GetName().c_str(), hSrcDS, psOptions, nullptr));
GDALTranslateOptionsFree(psOptions);
if (!poRetDS)
return false;
m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
return true;
}
//! @endcond

View File

@ -0,0 +1,45 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "write" step of "raster 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_RASTER_WRITE_INCLUDED
#define GDALALG_RASTER_WRITE_INCLUDED
#include "gdalalg_raster_pipeline.h"
//! @cond Doxygen_Suppress
/************************************************************************/
/* GDALRasterWriteAlgorithm */
/************************************************************************/
class GDALRasterWriteAlgorithm final : public GDALRasterPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "write";
static constexpr const char *DESCRIPTION = "Write a raster dataset.";
static constexpr const char *HELP_URL =
"https://gdal.org/en/stable/programs/gdal_raster_pipeline.html";
static std::vector<std::string> GetAliases()
{
return {};
}
GDALRasterWriteAlgorithm();
private:
bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
};
//! @endcond
#endif /* GDALALG_RASTER_WRITE_INCLUDED */

View File

@ -2803,6 +2803,17 @@ TEST_F(test_gdal_algorithm, vector_pipeline_GetUsageForCLI)
pipeline->GetUsageForCLI(true);
}
TEST_F(test_gdal_algorithm, raster_pipeline_GetUsageForCLI)
{
auto &singleton = GDALGlobalAlgorithmRegistry::GetSingleton();
auto raster = singleton.Instantiate("raster");
ASSERT_NE(raster, nullptr);
auto pipeline = raster->InstantiateSubAlgorithm("pipeline");
ASSERT_NE(pipeline, nullptr);
pipeline->GetUsageForCLI(false);
pipeline->GetUsageForCLI(true);
}
TEST_F(test_gdal_algorithm, registry_c_api)
{
auto reg = GDALGetGlobalAlgorithmRegistry();

View File

@ -0,0 +1,541 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal raster pipeline' 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_pipeline_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
raster = reg.InstantiateAlg("raster")
return raster.InstantiateSubAlgorithm("pipeline")
def test_gdalalg_raster_pipeline_read_and_write(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
last_pct = [0]
def my_progress(pct, msg, user_data):
last_pct[0] = pct
return True
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "write", out_filename], my_progress
)
assert last_pct[0] == 1.0
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
with pytest.raises(Exception, match="can only be called once per instance"):
pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "write", out_filename], my_progress
)
def test_gdalalg_raster_pipeline_pipeline_arg(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
# Also test extra pipes / exclamation mark
assert pipeline.ParseRunAndFinalize(
["--pipeline", f"! read ../gcore/data/byte.tif | | write {out_filename} !"]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_as_api(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
pipeline.GetArg("pipeline").Set(
f"read ../gcore/data/byte.tif ! write {out_filename}"
)
assert pipeline.Run()
ds = pipeline.GetArg("output").Get().GetDataset()
assert ds.GetRasterBand(1).Checksum() == 4672
assert pipeline.Finalize()
ds = None
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_input_through_api(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
pipeline.GetArg("input").Get().SetDataset(gdal.OpenEx("../gcore/data/byte.tif"))
pipeline.GetArg("pipeline").Set(f"read ! write {out_filename}")
assert pipeline.Run()
assert pipeline.Finalize()
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_input_through_api_run_twice(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
pipeline.GetArg("input").Get().SetDataset(gdal.OpenEx("../gcore/data/byte.tif"))
pipeline.GetArg("pipeline").Set(f"read ! write {out_filename}")
assert pipeline.Run()
with pytest.raises(
Exception, match=r"pipeline: Step nr 0 \(read\) has already an output dataset"
):
pipeline.Run()
def test_gdalalg_raster_pipeline_output_through_api(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
pipeline.GetArg("output").Get().SetName(out_filename)
pipeline.GetArg("pipeline").Set("read ../gcore/data/byte.tif ! write")
assert pipeline.Run()
assert pipeline.Finalize()
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_as_api_error():
pipeline = get_pipeline_alg()
pipeline.GetArg("pipeline").Set("read")
with pytest.raises(Exception, match="pipeline: At least 2 steps must be provided"):
pipeline.Run()
def test_gdalalg_raster_pipeline_usage_as_json():
pipeline = get_pipeline_alg()
j = json.loads(pipeline.GetUsageAsJSON())
assert "pipeline_algorithms" in j
def test_gdalalg_raster_pipeline_quoted(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
[f"read ../gcore/data/byte.tif ! write {out_filename}"]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_progress(tmp_path):
out_filename = str(tmp_path / "out.tif")
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 pipeline --progress read ../gcore/data/byte.tif ! write {out_filename}"
)
assert out.startswith(
"0...10...20...30...40...50...60...70...80...90...100 - done."
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_easter_egg(tmp_path):
out_filename = str(tmp_path / "out.tif")
import gdaltest
import test_cli_utilities
gdal_path = test_cli_utilities.get_gdal_path()
if gdal_path is None:
pytest.skip("gdal binary missing")
gdaltest.runexternal(
f"{gdal_path} raster +gdal=pipeline +step +gdal=read +input=../gcore/data/byte.tif +step +write +output={out_filename}"
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_usage_as_json_bis():
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 pipeline --json-usage"
)
j = json.loads(out)
assert "pipeline_algorithms" in j
def test_gdalalg_raster_pipeline_missing_at_run():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: 'pipeline' argument not set"):
pipeline.Run()
def test_gdalalg_raster_pipeline_empty_args():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: At least 2 steps must be provided"):
pipeline.ParseRunAndFinalize([])
def test_gdalalg_raster_pipeline_unknow_step():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: unknown step name: unknown_step"):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"unknown_step",
"!",
"write",
"/vsimem/foo.tif",
]
)
def test_gdalalg_raster_pipeline_read_read():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: Last step should be 'write'"):
pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "read", "../gcore/data/byte.tif"]
)
def test_gdalalg_raster_pipeline_write_write():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: First step should be 'read'"):
pipeline.ParseRunAndFinalize(
["write", "/vsimem/out.tif", "!", "write", "/vsimem/out.tif"]
)
def test_gdalalg_raster_pipeline_read_write_write():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: Only last step can be 'write'"):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"write",
"/vsimem/out.tif",
"!",
"write",
"/vsimem/out.tif",
]
)
def test_gdalalg_raster_pipeline_read_read_write():
pipeline = get_pipeline_alg()
with pytest.raises(Exception, match="pipeline: Only first step can be 'read'"):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"read",
"../gcore/data/byte.tif",
"!",
"write",
"/vsimem/out.tif",
]
)
def test_gdalalg_raster_pipeline_invalid_step_during_parsing(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
with pytest.raises(
Exception, match="write: Long name option '--invalid' is unknown"
):
pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "write", "--invalid", out_filename]
)
def test_gdalalg_raster_pipeline_invalid_step_during_validation(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
with pytest.raises(
Exception,
match="read: Positional arguments starting at 'INPUT' have not been specified",
):
pipeline.ParseRunAndFinalize(["read", "!", "write", "--invalid", out_filename])
@pytest.mark.require_driver("GPKG")
def test_gdalalg_raster_pipeline_write_options(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "write", "--of=GPKG", out_filename]
)
pipeline = get_pipeline_alg()
with pytest.raises(
Exception,
match="already exists. Specify the --overwrite option to overwrite it",
):
assert pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "write", out_filename]
)
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
["read", "../gcore/data/byte.tif", "!", "write", "--overwrite", out_filename]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4672
@pytest.mark.require_driver("GPKG")
def test_gdalalg_raster_pipeline_write_co(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.gpkg")
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"write",
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
def test_gdalalg_raster_pipeline_reproject_invalid_src_crs(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
with pytest.raises(
Exception,
match="reproject: Invalid value for '--src-crs'",
):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"--src-crs=invalid",
"--dst-crs=EPSG:4326",
"!",
"write",
out_filename,
]
)
def test_gdalalg_raster_pipeline_reproject_invalid_dst_crs(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
with pytest.raises(
Exception,
match="reproject: Invalid value for '--dst-crs'",
):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"--dst-crs=invalid",
"!",
"write",
out_filename,
]
)
def test_gdalalg_raster_pipeline_reproject_invalid_resolution(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
with pytest.raises(
Exception,
match="Target resolution should be strictly positive",
):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"--resolution=1,-1",
"!",
"write",
out_filename,
]
)
def test_gdalalg_raster_pipeline_reproject_no_args(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"!",
"write",
"--overwrite",
out_filename,
]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetSpatialRef().GetAuthorityCode(None) == "26711"
assert ds.GetRasterBand(1).Checksum() == 4672
def test_gdalalg_raster_pipeline_reproject_invalid_extent(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
with pytest.raises(
Exception,
match="Value of 'extent' should be xmin,ymin,xmax,ymax with xmin <= xmax and ymin <= ymax",
):
pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"--extent=3,4,2,1",
"!",
"write",
out_filename,
]
)
def test_gdalalg_raster_pipeline_reproject_extent_arg(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"--src-crs=EPSG:32611",
"--dst-crs=EPSG:4326",
"--extent=-117.641,33.89,-117.628,33.9005",
"!",
"write",
"--overwrite",
out_filename,
]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetSpatialRef().GetAuthorityCode(None) == "4326"
assert ds.GetGeoTransform() == pytest.approx(
(-117.641, 0.0005909090909093286, 0.0, 33.9005, 0.0, -0.0005833333333333554)
)
assert ds.GetRasterBand(1).Checksum() == 4585
def test_gdalalg_raster_pipeline_reproject_almost_all_args(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_pipeline_alg()
assert pipeline.ParseRunAndFinalize(
[
"read",
"../gcore/data/byte.tif",
"!",
"reproject",
"--src-crs=EPSG:32611",
"--dst-crs=EPSG:4326",
"--resampling=bilinear",
"--resolution=0.0005,0.0004",
"--target-aligned-pixels",
"!",
"write",
"--overwrite",
out_filename,
]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetSpatialRef().GetAuthorityCode(None) == "4326"
assert ds.GetGeoTransform() == pytest.approx(
(-117.641, 0.0005, 0.0, 33.9008, 0.0, -0.0004), rel=1e-8
)
assert ds.GetRasterBand(1).Checksum() == 8515

View File

@ -0,0 +1,64 @@
#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# Project: GDAL/OGR Test Suite
# Purpose: 'gdal raster reproject' 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_reproject_alg():
reg = gdal.GetGlobalAlgorithmRegistry()
raster = reg.InstantiateAlg("raster")
return raster.InstantiateSubAlgorithm("reproject")
def test_gdalalg_raster_reproject(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
last_pct = [0]
def my_progress(pct, msg, user_data):
last_pct[0] = pct
return True
pipeline = get_reproject_alg()
assert pipeline.ParseRunAndFinalize(
[
"--src-crs=EPSG:32611",
"--dst-crs=EPSG:4326",
"../gcore/data/byte.tif",
out_filename,
],
my_progress,
)
assert last_pct[0] == 1.0
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRasterBand(1).Checksum() == 4727
def test_gdalalg_raster_reproject_failure(tmp_vsimem):
out_filename = str(tmp_vsimem / "out.tif")
pipeline = get_reproject_alg()
with pytest.raises(Exception, match="Unable to compute a transformation"):
pipeline.ParseRunAndFinalize(
[
"--src-crs=EPSG:32611",
"--dst-crs=EPSG:4326",
"../gcore/data/nan32.tif",
out_filename,
],
)

View File

@ -229,6 +229,20 @@ man_pages = [
[author_evenr],
1,
),
(
"programs/gdal_raster_pipeline",
"gdal-raster-pipeline",
"Process a raster dataset",
[author_evenr],
1,
),
(
"programs/gdal_raster_reproject",
"gdal-raster-reproject",
"Reproect a raster dataset",
[author_evenr],
1,
),
(
"programs/gdal_vector",
"gdal-vector",

View File

@ -21,12 +21,16 @@ Synopsis
where <SUBCOMMAND> is one of:
- convert: Convert a raster dataset.
- info: Return information on a raster dataset.
- pipeline: Process a raster dataset.
- reproject: Reproject a raster dataset.
Available sub-commands
----------------------
- :ref:`gdal_raster_info_subcommand`
- :ref:`gdal_raster_convert_subcommand`
- :ref:`gdal_raster_pipeline_subcommand`
- :ref:`gdal_raster_reproject_subcommand`
Examples
--------

View File

@ -0,0 +1,98 @@
.. _gdal_raster_pipeline_subcommand:
================================================================================
"gdal raster pipeline" sub-command
================================================================================
.. versionadded:: 3.11
.. only:: html
Process a raster dataset.
.. Index:: gdal raster pipeline
Synopsis
--------
.. code-block::
Usage: gdal raster pipeline [OPTIONS] <PIPELINE>
Process a raster dataset.
Positional arguments:
Common Options:
-h, --help Display help message and exit
--json-usage Display usage as JSON document and exit
--progress Display progress bar
<PIPELINE> is of the form: read [READ-OPTIONS] ( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]
A pipeline chains several steps, separated with the `!` (quotation mark) character.
The first step must be ``read``, and the last one ``write``.
Potential steps are:
* read [OPTIONS] <INPUT>
.. code-block::
Read a raster dataset.
Positional arguments:
-i, --input <INPUT> Input raster dataset [required]
Advanced Options:
--if, --input-format <INPUT-FORMAT> Input formats [may be repeated]
--oo, --open-option <KEY=VALUE> Open options [may be repeated]
* reproject [OPTIONS]
.. code-block::
Reproject a raster dataset.
Options:
-s, --src-crs <SRC-CRS> Source CRS
-d, --dst-crs <DST-CRS> Destination CRS
-r, --resampling <RESAMPLING> Resampling method. RESAMPLING=near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|max|med|q1|q3|sum
--resolution <xres>,<yres> Target resolution (in destination CRS units)
--extent <xmin>,<ymin>,<xmax>,<ymax> Target extent (in destination CRS units)
--target-aligned-pixels Round target extent to target resolution
* write [OPTIONS] <OUTPUT>
.. code-block::
Write a raster dataset.
Positional arguments:
-o, --output <OUTPUT> Output raster dataset [required]
Options:
-f, --of, --format, --output-format <OUTPUT-FORMAT> Output format
--co, --creation-option <KEY>=<VALUE> Creation option [may be repeated]
--overwrite Whether overwriting existing output is allowed
Description
-----------
:program:`gdal raster pipeline` can be used to process a raster dataset and
perform various on-the-fly processing steps.
Examples
--------
.. example::
:title: Reproject a GeoTIFF file to CRS EPSG:32632 ("WGS 84 / UTM zone 32N")
.. code-block:: bash
$ gdal raster pipeline --progress ! read in.tif ! reproject --dst-crs=EPSG:32632 ! write out.tif --overwrite

View File

@ -0,0 +1,64 @@
.. _gdal_raster_reproject_subcommand:
================================================================================
"gdal raster reproject" sub-command
================================================================================
.. versionadded:: 3.11
.. only:: html
Reproject a raster dataset.
.. Index:: gdal raster reproject
Synopsis
--------
.. code-block::
Usage: gdal raster reproject [OPTIONS] <INPUT> <OUTPUT>
Reproject a raster dataset.
Positional arguments:
-i, --input <INPUT> Input raster dataset [required]
-o, --output <OUTPUT> Output raster dataset [required]
Common Options:
-h, --help Display help message and exit
--version Display GDAL version and exit
--json-usage Display usage as JSON document and exit
--drivers Display driver list as JSON document and exit
--progress Display progress bar
Options:
-f, --of, --format, --output-format <OUTPUT-FORMAT> Output format
--co, --creation-option <KEY>=<VALUE> Creation option [may be repeated]
--overwrite Whether overwriting existing output is allowed
-s, --src-crs <SRC-CRS> Source CRS
-d, --dst-crs <DST-CRS> Destination CRS
-r, --resampling <RESAMPLING> Resampling method. RESAMPLING=near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|max|med|q1|q3|sum
--resolution <xres>,<yres> Target resolution (in destination CRS units)
--extent <xmin>,<ymin>,<xmax>,<ymax> Target extent (in destination CRS units)
--target-aligned-pixels Round target extent to target resolution
Advanced Options:
--if, --input-format <INPUT-FORMAT> Input formats [may be repeated]
--oo, --open-option <KEY=VALUE> Open options [may be repeated]
Description
-----------
:program:`gdal raster reproject` can be used to reproject a raster dataset.
Examples
--------
.. example::
:title: Reproject a GeoTIFF file to CRS EPSG:32632 ("WGS 84 / UTM zone 32N")
.. code-block:: bash
$ gdal raster reproject --dst-crs=EPSG:32632 in.tif out.tif --overwrite

View File

@ -31,6 +31,8 @@ single :program:`gdal` program that accepts commands and subcommands.
gdal_raster
gdal_raster_info
gdal_raster_convert
gdal_raster_pipeline
gdal_raster_reproject
gdal_vector
gdal_vector_info
gdal_vector_convert
@ -44,6 +46,8 @@ single :program:`gdal` program that accepts commands and subcommands.
- :ref:`gdal_raster_command`: Entry point for raster commands
- :ref:`gdal_raster_info_subcommand`: Get information on a raster dataset
- :ref:`gdal_raster_convert_subcommand`: Convert a raster dataset
- :ref:`gdal_raster_pipeline_subcommand`: Process a raster dataset
- :ref:`gdal_raster_reproject_subcommand`: Reproject a raster dataset
- :ref:`gdal_vector_command`: Entry point for vector commands
- :ref:`gdal_vector_info_subcommand`: Get information on a vector dataset
- :ref:`gdal_vector_convert_subcommand`: Convert a vector dataset