diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index fefb8d67791d..ac55e8ce9019 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -22,6 +22,7 @@ add_library( gdalalg_raster_overview_delete.cpp gdalalg_raster_read.cpp gdalalg_raster_reproject.cpp + gdalalg_raster_resize.cpp gdalalg_raster_stack.cpp gdalalg_raster_write.cpp gdalalg_vector.cpp diff --git a/apps/gdalalg_raster.cpp b/apps/gdalalg_raster.cpp index 769c9a9b4850..c4cc12e5c590 100644 --- a/apps/gdalalg_raster.cpp +++ b/apps/gdalalg_raster.cpp @@ -21,6 +21,7 @@ #include "gdalalg_raster_overview.h" #include "gdalalg_raster_pipeline.h" #include "gdalalg_raster_reproject.h" +#include "gdalalg_raster_resize.h" #include "gdalalg_raster_stack.h" /************************************************************************/ @@ -50,6 +51,7 @@ class GDALRasterAlgorithm final : public GDALAlgorithm RegisterSubAlgorithm(); RegisterSubAlgorithm(); RegisterSubAlgorithm(); + RegisterSubAlgorithm(); RegisterSubAlgorithm(); } diff --git a/apps/gdalalg_raster_pipeline.cpp b/apps/gdalalg_raster_pipeline.cpp index 45b4214f7f45..d836f8bf57e1 100644 --- a/apps/gdalalg_raster_pipeline.cpp +++ b/apps/gdalalg_raster_pipeline.cpp @@ -15,6 +15,7 @@ #include "gdalalg_raster_clip.h" #include "gdalalg_raster_edit.h" #include "gdalalg_raster_reproject.h" +#include "gdalalg_raster_resize.h" #include "gdalalg_raster_write.h" #include "cpl_conv.h" @@ -165,6 +166,7 @@ GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm( m_stepRegistry.Register(); m_stepRegistry.Register(); m_stepRegistry.Register(); + m_stepRegistry.Register(); } /************************************************************************/ diff --git a/apps/gdalalg_raster_reproject.cpp b/apps/gdalalg_raster_reproject.cpp index 5e09c07bd204..4f5ae00dc5df 100644 --- a/apps/gdalalg_raster_reproject.cpp +++ b/apps/gdalalg_raster_reproject.cpp @@ -39,6 +39,7 @@ GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep) .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos", "average", "rms", "mode", "min", "max", "med", "q1", "q3", "sum") + .SetDefault("nearest") .SetHiddenChoices("near"); auto &resArg = diff --git a/apps/gdalalg_raster_resize.cpp b/apps/gdalalg_raster_resize.cpp new file mode 100644 index 000000000000..2496bf5828ec --- /dev/null +++ b/apps/gdalalg_raster_resize.cpp @@ -0,0 +1,103 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: "resize" step of "raster pipeline" + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#include "gdalalg_raster_resize.h" + +#include "gdal_priv.h" +#include "gdal_utils.h" + +//! @cond Doxygen_Suppress + +#ifndef _ +#define _(x) (x) +#endif + +/************************************************************************/ +/* GDALRasterResizeAlgorithm::GDALRasterResizeAlgorithm() */ +/************************************************************************/ + +GDALRasterResizeAlgorithm::GDALRasterResizeAlgorithm(bool standaloneStep) + : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL, + standaloneStep) +{ + auto &sizeArg = AddArg("size", 0, _("Target size in pixels"), &m_size) + .SetMinCount(2) + .SetMaxCount(2) + .SetRequired() + .SetRepeatedArgAllowed(false) + .SetDisplayHintAboutRepetition(false) + .SetMetaVar(",") + .SetMutualExclusionGroup("resolution-size"); + sizeArg.AddValidationAction( + [&sizeArg]() + { + const auto &val = sizeArg.Get>(); + CPLAssert(val.size() == 2); + if (!(val[0] >= 0 && val[1] >= 0)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Target size should be positive or 0."); + return false; + } + return true; + }); + + AddArg("resampling", 'r', _("Resampling method"), &m_resampling) + .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos", + "average", "mode") + .SetDefault("nearest") + .SetHiddenChoices("near"); +} + +/************************************************************************/ +/* GDALRasterResizeAlgorithm::RunStep() */ +/************************************************************************/ + +bool GDALRasterResizeAlgorithm::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_size.empty()) + { + aosOptions.AddString("-outsize"); + aosOptions.AddString(CPLSPrintf("%d", m_size[0])); + aosOptions.AddString(CPLSPrintf("%d", m_size[1])); + } + if (!m_resampling.empty()) + { + aosOptions.AddString("-r"); + aosOptions.AddString(m_resampling.c_str()); + } + + GDALTranslateOptions *psOptions = + GDALTranslateOptionsNew(aosOptions.List(), nullptr); + + auto poOutDS = std::unique_ptr(GDALDataset::FromHandle( + GDALTranslate(m_outputDataset.GetName().c_str(), + GDALDataset::ToHandle(m_inputDataset.GetDatasetRef()), + psOptions, nullptr))); + GDALTranslateOptionsFree(psOptions); + const bool bRet = poOutDS != nullptr; + if (poOutDS) + { + m_outputDataset.Set(std::move(poOutDS)); + } + + return bRet; +} + +//! @endcond diff --git a/apps/gdalalg_raster_resize.h b/apps/gdalalg_raster_resize.h new file mode 100644 index 000000000000..6eb3fa430da9 --- /dev/null +++ b/apps/gdalalg_raster_resize.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: "resize" step of "raster pipeline" + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef GDALALG_RASTER_RESIZE_INCLUDED +#define GDALALG_RASTER_RESIZE_INCLUDED + +#include "gdalalg_raster_pipeline.h" + +//! @cond Doxygen_Suppress + +/************************************************************************/ +/* GDALRasterResizeAlgorithm */ +/************************************************************************/ + +class GDALRasterResizeAlgorithm /* non final */ + : public GDALRasterPipelineStepAlgorithm +{ + public: + static constexpr const char *NAME = "resize"; + static constexpr const char *DESCRIPTION = + "Resize a raster dataset without changing the georeferenced extents."; + static constexpr const char *HELP_URL = "/programs/gdal_raster_resize.html"; + + static std::vector GetAliases() + { + return {}; + } + + explicit GDALRasterResizeAlgorithm(bool standaloneStep = false); + + private: + bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override; + + std::vector m_size{}; + std::string m_resampling{}; +}; + +/************************************************************************/ +/* GDALRasterResizeAlgorithmStandalone */ +/************************************************************************/ + +class GDALRasterResizeAlgorithmStandalone final + : public GDALRasterResizeAlgorithm +{ + public: + GDALRasterResizeAlgorithmStandalone() + : GDALRasterResizeAlgorithm(/* standaloneStep = */ true) + { + } +}; + +//! @endcond + +#endif /* GDALALG_RASTER_RESIZE_INCLUDED */ diff --git a/autotest/utilities/test_gdalalg_raster_resize.py b/autotest/utilities/test_gdalalg_raster_resize.py new file mode 100644 index 000000000000..4e77a3997604 --- /dev/null +++ b/autotest/utilities/test_gdalalg_raster_resize.py @@ -0,0 +1,76 @@ +#!/usr/bin/env pytest +# -*- coding: utf-8 -*- +############################################################################### +# Project: GDAL/OGR Test Suite +# Purpose: 'gdal raster resize' testing +# Author: Even Rouault +# +############################################################################### +# Copyright (c) 2025, Even Rouault +# +# SPDX-License-Identifier: MIT +############################################################################### + +from osgeo import gdal + + +def get_resize_alg(): + reg = gdal.GetGlobalAlgorithmRegistry() + raster = reg.InstantiateAlg("raster") + return raster.InstantiateSubAlgorithm("resize") + + +def test_gdalalg_raster_resize(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_resize_alg() + assert pipeline.ParseRunAndFinalize( + [ + "--size=10,0", + "../gcore/data/byte.tif", + out_filename, + ], + my_progress, + ) + assert last_pct[0] == 1.0 + + with gdal.OpenEx(out_filename) as ds: + assert ds.RasterXSize == 10 + assert ds.RasterYSize == 10 + assert ds.GetRasterBand(1).Checksum() == 1192 + + +def test_gdalalg_raster_resize_resampling(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_resize_alg() + assert pipeline.ParseRunAndFinalize( + [ + "--size=0,10", + "-r", + "cubic", + "../gcore/data/byte.tif", + out_filename, + ], + my_progress, + ) + assert last_pct[0] == 1.0 + + with gdal.OpenEx(out_filename) as ds: + assert ds.RasterXSize == 10 + assert ds.RasterYSize == 10 + assert ds.GetRasterBand(1).Checksum() == 1059 diff --git a/doc/source/conf.py b/doc/source/conf.py index 0ef43b9f837b..1d91b22c32f6 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -278,6 +278,13 @@ [author_evenr], 1, ), + ( + "programs/gdal_raster_resize", + "gdal-raster-resize", + "Resize a raster dataset", + [author_evenr], + 1, + ), ( "programs/gdal_raster_stack", "gdal-raster-stack", diff --git a/doc/source/programs/gdal_raster.rst b/doc/source/programs/gdal_raster.rst index bcc331dce415..568306377eb9 100644 --- a/doc/source/programs/gdal_raster.rst +++ b/doc/source/programs/gdal_raster.rst @@ -28,6 +28,7 @@ Synopsis - overview: Manage overviews of a raster dataset. - pipeline: Process a raster dataset. - reproject: Reproject a raster dataset. + - resize: Resize a raster dataset. - stack: Combine together input bands into a multi-band output, either virtual (VRT) or materialized. @@ -42,6 +43,7 @@ Available sub-commands - :ref:`gdal_raster_overview_subcommand` - :ref:`gdal_raster_pipeline_subcommand` - :ref:`gdal_raster_reproject_subcommand` +- :ref:`gdal_raster_resize_subcommand` - :ref:`gdal_raster_stack_subcommand` Examples diff --git a/doc/source/programs/gdal_raster_pipeline.rst b/doc/source/programs/gdal_raster_pipeline.rst index d788909d0e81..f6c31d8cdcdd 100644 --- a/doc/source/programs/gdal_raster_pipeline.rst +++ b/doc/source/programs/gdal_raster_pipeline.rst @@ -87,13 +87,25 @@ Details for options can be found in :ref:`gdal_raster_edit_subcommand`. Options: -s, --src-crs Source CRS -d, --dst-crs Destination CRS - -r, --resampling Resampling method. RESAMPLING=near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|max|med|q1|q3|sum + -r, --resampling Resampling method. RESAMPLING=near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|max|med|q1|q3|sum (default: nearest) --resolution , Target resolution (in destination CRS units) --bbox ,,, Target bounding box (in destination CRS units) --target-aligned-pixels Round target extent to target resolution Details for options can be found in :ref:`gdal_raster_reproject_subcommand`. +* resize [OPTIONS] + +.. code-block:: + + Resize a raster dataset without changing the georeferenced extents. + + Options: + --size , Target size in pixels [required] + -r, --resampling Resampling method. RESAMPLING=nearest|bilinear|cubic|cubicspline|lanczos|average|mode (default: nearest) + +Details for options can be found in :ref:`gdal_raster_resize_subcommand`. + * write [OPTIONS] .. code-block:: diff --git a/doc/source/programs/gdal_raster_reproject.rst b/doc/source/programs/gdal_raster_reproject.rst index 6a27f2dde6de..079151568546 100644 --- a/doc/source/programs/gdal_raster_reproject.rst +++ b/doc/source/programs/gdal_raster_reproject.rst @@ -38,7 +38,7 @@ Synopsis --overwrite Whether overwriting existing output is allowed -s, --src-crs Source CRS -d, --dst-crs Destination CRS - -r, --resampling Resampling method. RESAMPLING=nearest|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|max|med|q1|q3|sum + -r, --resampling Resampling method. RESAMPLING=nearest|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|max|med|q1|q3|sum (default: nearest) --resolution , Target resolution (in destination CRS units) --bbox ,,, Target bounding box (in destination CRS units) --target-aligned-pixels Round target extent to target resolution diff --git a/doc/source/programs/gdal_raster_resize.rst b/doc/source/programs/gdal_raster_resize.rst new file mode 100644 index 000000000000..6f046ba7b49b --- /dev/null +++ b/doc/source/programs/gdal_raster_resize.rst @@ -0,0 +1,93 @@ +.. _gdal_raster_resize_subcommand: + +================================================================================ +"gdal raster resize" sub-command +================================================================================ + +.. versionadded:: 3.11 + +.. only:: html + + Resize a raster dataset without changing the georeferenced extents. + +.. Index:: gdal raster resize + +Synopsis +-------- + +.. code-block:: + + Usage: gdal raster resize [OPTIONS] + + Resize a raster dataset. + + Positional arguments: + -i, --input Input raster dataset [required] + -o, --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 + --config = Configuration option [may be repeated] + --progress Display progress bar + + Options: + -f, --of, --format, --output-format Output format + --co, --creation-option = Creation option [may be repeated] + --overwrite Whether overwriting existing output is allowed + --size , Target size in pixels [required] + -r, --resampling Resampling method. RESAMPLING=nearest|bilinear|cubic|cubicspline|lanczos|average|mode (default: nearest) + + + Advanced Options: + --if, --input-format Input formats [may be repeated] + --oo, --open-option Open options [may be repeated] + + +Description +----------- + +:program:`gdal raster resize` can be used to resize a raster dataset without +changing the georeferenced extents. + +Standard options +++++++++++++++++ + +.. include:: gdal_options/of_raster_create_copy.rst + +.. include:: gdal_options/co.rst + +.. include:: gdal_options/overwrite.rst + +.. option:: --size , + + Set output file size in pixels and lines. If width or height is set to 0, + the other dimension will be guessed from the computed resolution. + +.. option:: -r, --resampling + + Resampling method to use. Available methods are: + + ``near``: nearest neighbour resampling (default, fastest algorithm, worst interpolation quality). + + ``bilinear``: bilinear resampling. + + ``cubic``: cubic resampling. + + ``cubicspline``: cubic spline resampling. + + ``lanczos``: Lanczos windowed sinc resampling. + + ``average``: average resampling, computes the weighted average of all non-NODATA contributing pixels. + +Examples +-------- + +.. example:: + :title: Resize a dataset to 1000 columns and 500 lines using cubic resampling + + .. code-block:: bash + + $ gdal raster resize --resize=1000,500 -r cubic in.tif out.tif --overwrite diff --git a/doc/source/programs/index.rst b/doc/source/programs/index.rst index 7a7927d3a6cb..03e64b064892 100644 --- a/doc/source/programs/index.rst +++ b/doc/source/programs/index.rst @@ -42,6 +42,7 @@ single :program:`gdal` program that accepts commands and subcommands. gdal_raster_overview_delete gdal_raster_pipeline gdal_raster_reproject + gdal_raster_resize gdal_raster_stack gdal_vector gdal_vector_info @@ -71,6 +72,7 @@ single :program:`gdal` program that accepts commands and subcommands. - :ref:`gdal_raster_overview_delete_subcommand`: Remove overviews of a raster dataset - :ref:`gdal_raster_pipeline_subcommand`: Process a raster dataset - :ref:`gdal_raster_reproject_subcommand`: Reproject a raster dataset + - :ref:`gdal_raster_resize_subcommand`: Resize a raster dataset without changing the georeferenced extents - :ref:`gdal_raster_stack_subcommand`: Combine together input bands into a multi-band output, either virtual (VRT) or materialized. - :ref:`gdal_vector_command`: Entry point for vector commands - :ref:`gdal_vector_info_subcommand`: Get information on a vector dataset @@ -78,7 +80,7 @@ single :program:`gdal` program that accepts commands and subcommands. - :ref:`gdal_vector_filter_subcommand`: Filter a vector dataset - :ref:`gdal_vector_convert_subcommand`: Convert a vector dataset - :ref:`gdal_vector_pipeline_subcommand`: Process a vector dataset - - :ref:`gdal_vector_select_subcommand`: + - :ref:`gdal_vector_select_subcommand`: Select a subset of fields from a vector dataset. - :ref:`gdal_vector_sql_subcommand`: Apply SQL statement(s) to a dataset