Skip to content

Commit

Permalink
Add a 'gdal raster edit' command to override SRS, extent and metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
rouault committed Dec 12, 2024
1 parent 49367db commit e4bb0c3
Showing 18 changed files with 999 additions and 60 deletions.
1 change: 1 addition & 0 deletions apps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ add_library(
gdalalg_raster.cpp
gdalalg_raster_info.cpp
gdalalg_raster_convert.cpp
gdalalg_raster_edit.cpp
gdalalg_raster_pipeline.cpp
gdalalg_raster_read.cpp
gdalalg_raster_reproject.cpp
44 changes: 29 additions & 15 deletions apps/gdal_translate_lib.cpp
Original file line number Diff line number Diff line change
@@ -760,7 +760,8 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset,
CPLFree(pszSRS);
}

if (!psOptions->osOutputSRS.empty())
if (!psOptions->osOutputSRS.empty() && psOptions->osOutputSRS != "null" &&
psOptions->osOutputSRS != "none")
{
OGRSpatialReference oOutputSRS;
if (oOutputSRS.SetFromUserInput(psOptions->osOutputSRS.c_str()) !=
@@ -1520,23 +1521,31 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset,

if (psOptions->nGCPCount == 0)
{
OGRSpatialReference oSRS;
if (!psOptions->osOutputSRS.empty())
if (psOptions->osOutputSRS == "null" ||
psOptions->osOutputSRS == "none")
{
oSRS.SetFromUserInput(psOptions->osOutputSRS.c_str());
oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
poVDS->SetSpatialRef(nullptr);
}
else
{
const OGRSpatialReference *poSrcSRS = poSrcDS->GetSpatialRef();
if (poSrcSRS)
oSRS = *poSrcSRS;
}
if (!oSRS.IsEmpty())
{
if (psOptions->dfOutputCoordinateEpoch > 0)
oSRS.SetCoordinateEpoch(psOptions->dfOutputCoordinateEpoch);
poVDS->SetSpatialRef(&oSRS);
OGRSpatialReference oSRS;
if (!psOptions->osOutputSRS.empty())
{
oSRS.SetFromUserInput(psOptions->osOutputSRS.c_str());
oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
}
else
{
const OGRSpatialReference *poSrcSRS = poSrcDS->GetSpatialRef();
if (poSrcSRS)
oSRS = *poSrcSRS;
}
if (!oSRS.IsEmpty())
{
if (psOptions->dfOutputCoordinateEpoch > 0)
oSRS.SetCoordinateEpoch(psOptions->dfOutputCoordinateEpoch);
poVDS->SetSpatialRef(&oSRS);
}
}
}

@@ -1598,7 +1607,12 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset,
if (psOptions->nGCPCount != 0)
{
OGRSpatialReference oSRS;
if (!psOptions->osOutputSRS.empty())
if (psOptions->osOutputSRS == "null" ||
psOptions->osOutputSRS == "none")
{
// nothing to do
}
else if (!psOptions->osOutputSRS.empty())
{
oSRS.SetFromUserInput(psOptions->osOutputSRS.c_str());
oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2 changes: 2 additions & 0 deletions apps/gdalalg_raster.cpp
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@

#include "gdalalg_raster_info.h"
#include "gdalalg_raster_convert.h"
#include "gdalalg_raster_edit.h"
#include "gdalalg_raster_pipeline.h"
#include "gdalalg_raster_reproject.h"

@@ -37,6 +38,7 @@ class GDALRasterAlgorithm final : public GDALAlgorithm
{
RegisterSubAlgorithm<GDALRasterInfoAlgorithm>();
RegisterSubAlgorithm<GDALRasterConvertAlgorithm>();
RegisterSubAlgorithm<GDALRasterEditAlgorithmStandalone>();
RegisterSubAlgorithm<GDALRasterPipelineAlgorithm>();
RegisterSubAlgorithm<GDALRasterReprojectAlgorithmStandalone>();
}
233 changes: 233 additions & 0 deletions apps/gdalalg_raster_edit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "edit" 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_edit.h"

#include "gdal_priv.h"
#include "gdal_utils.h"

//! @cond Doxygen_Suppress

#ifndef _
#define _(x) (x)
#endif

/************************************************************************/
/* GDALRasterEditAlgorithm::GDALRasterEditAlgorithm() */
/************************************************************************/

GDALRasterEditAlgorithm::GDALRasterEditAlgorithm(bool standaloneStep)
: GDALRasterPipelineStepAlgorithm(
NAME, DESCRIPTION, HELP_URL,
// Avoid automatic addition of input/output arguments
/*standaloneStep = */ false)
{
if (standaloneStep)
{
AddArg("dataset", 0, _("Dataset (in-place updated)"), &m_dataset,
GDAL_OF_RASTER | GDAL_OF_UPDATE)
.SetPositional()
.SetRequired();
m_standaloneStep = true;
}

AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
.AddHiddenAlias("a_srs")
.SetIsCRSArg(/*noneAllowed=*/true);

{
auto &arg =
AddArg("extent", 0, _("Extent as xmin,ymin,xmax,ymax"), &m_extent)
.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 'extent' should be xmin,ymin,xmax,ymax with "
"xmin <= xmax and ymin <= ymax");
return false;
}
return true;
});
}

{
auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
&m_metadata)
.SetMetaVar("<KEY>=<VALUE>");
arg.AddValidationAction([this, &arg]()
{ return ValidateKeyValue(arg); });
arg.AddHiddenAlias("mo");
}

AddArg("unset-metadata", 0, _("Remove dataset metadata item"),
&m_unsetMetadata)
.SetMetaVar("<KEY>");
}

/************************************************************************/
/* GDALRasterEditAlgorithm::RunImpl() */
/************************************************************************/

bool GDALRasterEditAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
void *pProgressData)
{
if (m_standaloneStep)
{
auto poDS = m_dataset.GetDatasetRef();
CPLAssert(poDS);
if (poDS->GetAccess() != GA_Update)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Dataset should be opened in update mode");
return false;
}

if (m_overrideCrs == "null" || m_overrideCrs == "none")
{
if (poDS->SetSpatialRef(nullptr) != CE_None)
{
ReportError(CE_Failure, CPLE_AppDefined,
"SetSpatialRef(%s) failed", m_overrideCrs.c_str());
return false;
}
}
else if (!m_overrideCrs.empty())
{
OGRSpatialReference oSRS;
oSRS.SetFromUserInput(m_overrideCrs.c_str());
oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
if (poDS->SetSpatialRef(&oSRS) != CE_None)
{
ReportError(CE_Failure, CPLE_AppDefined,
"SetSpatialRef(%s) failed", m_overrideCrs.c_str());
return false;
}
}

if (!m_extent.empty())
{
if (poDS->GetRasterXSize() == 0 || poDS->GetRasterYSize() == 0)
{
ReportError(
CE_Failure, CPLE_AppDefined,
"Cannot set extent because dataset has 0x0 dimension");
return false;
}
double adfGT[6];
adfGT[0] = m_extent[0];
adfGT[1] = (m_extent[2] - m_extent[0]) / poDS->GetRasterXSize();
adfGT[2] = 0;
adfGT[3] = m_extent[3];
adfGT[4] = 0;
adfGT[5] = -(m_extent[3] - m_extent[1]) / poDS->GetRasterYSize();
if (poDS->SetGeoTransform(adfGT) != CE_None)
{
ReportError(CE_Failure, CPLE_AppDefined,
"Setting extent failed");
return false;
}
}

const CPLStringList aosMD(m_metadata);
for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
{
if (poDS->SetMetadataItem(key, value) != CE_None)
{
ReportError(CE_Failure, CPLE_AppDefined,
"SetMetadataItem('%s', '%s') failed", key, value);
return false;
}
}

for (const std::string &key : m_unsetMetadata)
{
if (poDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
{
ReportError(CE_Failure, CPLE_AppDefined,
"SetMetadataItem('%s', NULL) failed", key.c_str());
return false;
}
}

return true;
}
else
{
return RunStep(pfnProgress, pProgressData);
}
}

/************************************************************************/
/* GDALRasterEditAlgorithm::RunStep() */
/************************************************************************/

bool GDALRasterEditAlgorithm::RunStep(GDALProgressFunc, void *)
{
CPLAssert(m_inputDataset.GetDatasetRef());
CPLAssert(m_outputDataset.GetName().empty());
CPLAssert(!m_outputDataset.GetDatasetRef());

CPLStringList aosOptions;
aosOptions.AddString("-of");
aosOptions.AddString("VRT");
if (!m_overrideCrs.empty())
{
aosOptions.AddString("-a_srs");
aosOptions.AddString(m_overrideCrs.c_str());
}
if (!m_extent.empty())
{
aosOptions.AddString("-a_ullr");
aosOptions.AddString(CPLSPrintf("%.17g", m_extent[0])); // upper-left X
aosOptions.AddString(CPLSPrintf("%.17g", m_extent[3])); // upper-left Y
aosOptions.AddString(
CPLSPrintf("%.17g", m_extent[2])); // lower-right X
aosOptions.AddString(
CPLSPrintf("%.17g", m_extent[1])); // lower-right Y
}

for (const auto &val : m_metadata)
{
aosOptions.AddString("-mo");
aosOptions.AddString(val.c_str());
}

for (const std::string &key : m_unsetMetadata)
{
aosOptions.AddString("-mo");
aosOptions.AddString((key + "=").c_str());
}

GDALTranslateOptions *psOptions =
GDALTranslateOptionsNew(aosOptions.List(), nullptr);

GDALDatasetH hSrcDS = GDALDataset::ToHandle(m_inputDataset.GetDatasetRef());
auto poRetDS =
GDALDataset::FromHandle(GDALTranslate("", hSrcDS, psOptions, nullptr));
GDALTranslateOptionsFree(psOptions);
const bool ok = poRetDS != nullptr;
if (ok)
m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));

return ok;
}

//! @endcond
65 changes: 65 additions & 0 deletions apps/gdalalg_raster_edit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/******************************************************************************
*
* Project: GDAL
* Purpose: "edit" 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_EDIT_INCLUDED
#define GDALALG_RASTER_EDIT_INCLUDED

#include "gdalalg_raster_pipeline.h"

//! @cond Doxygen_Suppress

/************************************************************************/
/* GDALRasterEditAlgorithm */
/************************************************************************/

class GDALRasterEditAlgorithm /* non final */
: public GDALRasterPipelineStepAlgorithm
{
public:
static constexpr const char *NAME = "edit";
static constexpr const char *DESCRIPTION = "Edit a raster dataset.";
static constexpr const char *HELP_URL = "/programs/gdal_raster_edit.html";

static std::vector<std::string> GetAliases()
{
return {};
}

explicit GDALRasterEditAlgorithm(bool standaloneStep = false);

private:
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;

GDALArgDatasetValue m_dataset{}; // standalone mode only
std::string m_overrideCrs{};
std::vector<double> m_extent{};
std::vector<std::string> m_metadata{};
std::vector<std::string> m_unsetMetadata{};
};

/************************************************************************/
/* GDALRasterEditAlgorithmStandalone */
/************************************************************************/

class GDALRasterEditAlgorithmStandalone final : public GDALRasterEditAlgorithm
{
public:
GDALRasterEditAlgorithmStandalone()
: GDALRasterEditAlgorithm(/* standaloneStep = */ true)
{
}
};

//! @endcond

#endif /* GDALALG_RASTER_EDIT_INCLUDED */
Loading

0 comments on commit e4bb0c3

Please sign in to comment.