diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index e37a04fc79f1..2432d600f4d1 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -16,6 +16,8 @@ add_library( gdalalg_raster_convert.cpp gdalalg_raster_edit.cpp gdalalg_raster_pipeline.cpp + gdalalg_raster_overview_add.cpp + gdalalg_raster_overview_delete.cpp gdalalg_raster_read.cpp gdalalg_raster_reproject.cpp gdalalg_raster_write.cpp diff --git a/apps/gdalalg_raster.cpp b/apps/gdalalg_raster.cpp index b2975197a303..47ddc0519ba0 100644 --- a/apps/gdalalg_raster.cpp +++ b/apps/gdalalg_raster.cpp @@ -16,6 +16,7 @@ #include "gdalalg_raster_info.h" #include "gdalalg_raster_convert.h" #include "gdalalg_raster_edit.h" +#include "gdalalg_raster_overview.h" #include "gdalalg_raster_pipeline.h" #include "gdalalg_raster_reproject.h" @@ -40,6 +41,7 @@ class GDALRasterAlgorithm final : public GDALAlgorithm RegisterSubAlgorithm(); RegisterSubAlgorithm(); RegisterSubAlgorithm(); + RegisterSubAlgorithm(); RegisterSubAlgorithm(); RegisterSubAlgorithm(); RegisterSubAlgorithm(); diff --git a/apps/gdalalg_raster_overview.h b/apps/gdalalg_raster_overview.h new file mode 100644 index 000000000000..3c466e93aec3 --- /dev/null +++ b/apps/gdalalg_raster_overview.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: gdal "raster overview" subcommand + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef GDALALG_RASTER_OVERVIEW_INCLUDED +#define GDALALG_RASTER_OVERVIEW_INCLUDED + +#include "gdalalgorithm.h" + +//! @cond Doxygen_Suppress + +#include "gdalalgorithm.h" + +#include "gdalalg_raster_overview_add.h" +#include "gdalalg_raster_overview_delete.h" + +/************************************************************************/ +/* GDALRasterOverviewAlgorithm */ +/************************************************************************/ + +class GDALRasterOverviewAlgorithm final : public GDALAlgorithm +{ + public: + static constexpr const char *NAME = "overview"; + static constexpr const char *DESCRIPTION = + "Manage overviews of a raster dataset."; + static constexpr const char *HELP_URL = + "/programs/gdal_raster_overview.html"; + + static std::vector GetAliases() + { + return {}; + } + + GDALRasterOverviewAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL) + { + RegisterSubAlgorithm(); + RegisterSubAlgorithm(); + } + + private: + bool RunImpl(GDALProgressFunc, void *) override + { + CPLError(CE_Failure, CPLE_AppDefined, + "The Run() method should not be called directly on the \"gdal " + "raster overview\" program."); + return false; + } +}; + +//! @endcond + +#endif diff --git a/apps/gdalalg_raster_overview_add.cpp b/apps/gdalalg_raster_overview_add.cpp new file mode 100644 index 000000000000..bae1437457e6 --- /dev/null +++ b/apps/gdalalg_raster_overview_add.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: gdal "raster overview add" subcommand + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#include "gdalalg_raster_overview_add.h" + +#include "cpl_string.h" +#include "gdal_priv.h" + +//! @cond Doxygen_Suppress + +#ifndef _ +#define _(x) (x) +#endif + +/************************************************************************/ +/* GDALRasterOverviewAlgorithmAdd() */ +/************************************************************************/ + +GDALRasterOverviewAlgorithmAdd::GDALRasterOverviewAlgorithmAdd() + : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL) +{ + AddProgressArg(); + AddOpenOptionsArg(&m_openOptions); + AddArg("dataset", 0, _("Dataset (in-place updated, unless --read-only)"), + &m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE) + .SetPositional() + .SetRequired(); + AddArg("external", 0, _("Add external overviews"), &m_readOnly) + .AddHiddenAlias("ro") + .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY); + + AddArg("resampling", 'r', _("Resampling method"), &m_resampling) + .SetChoices("nearest", "average", "cubic", "cubicspline", "lanczos", + "bilinear", "gauss", "average_magphase", "rms", "mode") + .SetHiddenChoices("near"); + + auto &levelArg = + AddArg("levels", 0, _("Levels / decimation factors"), &m_levels); + levelArg.AddValidationAction( + [this, &levelArg]() + { + const auto &values = levelArg.Get>(); + for (const auto &value : values) + { + if (value < 2) + { + ReportError(CE_Failure, CPLE_IllegalArg, + "Values of 'levels' argument should be " + "integers greater or equal to 2."); + return false; + } + } + return true; + }); + + auto &minSizeArg = + AddArg("min-size", 0, + _("Maximum width or height of the smallest overview level."), + &m_minSize); + minSizeArg.AddValidationAction( + [this, &minSizeArg]() + { + const int val = minSizeArg.Get(); + if (val <= 0) + { + ReportError(CE_Failure, CPLE_IllegalArg, + "Value of 'min-size' should be an integer greater " + "or equal to 1."); + return false; + } + return true; + }); +} + +/************************************************************************/ +/* GDALRasterOverviewAlgorithmAdd::RunImpl() */ +/************************************************************************/ + +bool GDALRasterOverviewAlgorithmAdd::RunImpl(GDALProgressFunc pfnProgress, + void *pProgressData) +{ + auto poDS = m_dataset.GetDatasetRef(); + CPLAssert(poDS); + + std::string resampling = m_resampling; + if (resampling.empty() && poDS->GetRasterCount() > 0) + { + auto poBand = poDS->GetRasterBand(1); + if (poBand->GetOverviewCount() > 0) + { + const char *pszResampling = + poBand->GetOverview(0)->GetMetadataItem("RESAMPLING"); + if (pszResampling) + { + resampling = pszResampling; + CPLDebug("GDAL", + "Reusing resampling method %s from existing " + "overview", + pszResampling); + } + } + } + if (resampling.empty()) + resampling = "nearest"; + + std::vector levels = m_levels; + + // If no levels are specified, reuse the potentially existing ones. + if (levels.empty() && poDS->GetRasterCount() > 0) + { + auto poBand = poDS->GetRasterBand(1); + const int nExistingCount = poBand->GetOverviewCount(); + if (nExistingCount > 0) + { + for (int iOvr = 0; iOvr < nExistingCount; ++iOvr) + { + auto poOverview = poBand->GetOverview(iOvr); + if (poOverview) + { + const int nOvFactor = GDALComputeOvFactor( + poOverview->GetXSize(), poBand->GetXSize(), + poOverview->GetYSize(), poBand->GetYSize()); + levels.push_back(nOvFactor); + } + } + } + } + + if (levels.empty()) + { + const int nXSize = poDS->GetRasterXSize(); + const int nYSize = poDS->GetRasterYSize(); + int nOvrFactor = 1; + while (DIV_ROUND_UP(nXSize, nOvrFactor) > m_minSize || + DIV_ROUND_UP(nYSize, nOvrFactor) > m_minSize) + { + nOvrFactor *= 2; + levels.push_back(nOvrFactor); + } + } + + return levels.empty() || + GDALBuildOverviews(GDALDataset::ToHandle(poDS), resampling.c_str(), + static_cast(levels.size()), levels.data(), 0, + nullptr, pfnProgress, pProgressData) == CE_None; +} + +//! @endcond diff --git a/apps/gdalalg_raster_overview_add.h b/apps/gdalalg_raster_overview_add.h new file mode 100644 index 000000000000..ae54c262e5c9 --- /dev/null +++ b/apps/gdalalg_raster_overview_add.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: gdal "raster overview add" subcommand + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef GDALALG_RASTER_OVERVIEW_ADD_INCLUDED +#define GDALALG_RASTER_OVERVIEW_ADD_INCLUDED + +#include "gdalalgorithm.h" + +//! @cond Doxygen_Suppress + +/************************************************************************/ +/* GDALRasterOverviewAlgorithmAdd */ +/************************************************************************/ + +class GDALRasterOverviewAlgorithmAdd final : public GDALAlgorithm +{ + public: + static constexpr const char *NAME = "add"; + static constexpr const char *DESCRIPTION = "Adding overviews."; + static constexpr const char *HELP_URL = + "/programs/gdal_raster_overview_add.html"; + + static std::vector GetAliases() + { + return {}; + } + + GDALRasterOverviewAlgorithmAdd(); + + private: + bool RunImpl(GDALProgressFunc, void *) override; + + GDALArgDatasetValue m_dataset{}; + std::vector m_openOptions{}; + std::vector m_inputFormats{}; + std::string m_resampling{}; + std::vector m_levels{}; + int m_minSize = 256; + bool m_readOnly = false; +}; + +//! @endcond + +#endif diff --git a/apps/gdalalg_raster_overview_delete.cpp b/apps/gdalalg_raster_overview_delete.cpp new file mode 100644 index 000000000000..264532cd8f1f --- /dev/null +++ b/apps/gdalalg_raster_overview_delete.cpp @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: gdal "raster overview delete" subcommand + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#include "gdalalg_raster_overview_delete.h" + +#include "gdal_priv.h" + +//! @cond Doxygen_Suppress + +#ifndef _ +#define _(x) (x) +#endif + +/************************************************************************/ +/* GDALRasterOverviewAlgorithmDelete() */ +/************************************************************************/ + +GDALRasterOverviewAlgorithmDelete::GDALRasterOverviewAlgorithmDelete() + : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL) +{ + AddProgressArg(); + AddOpenOptionsArg(&m_openOptions); + AddArg("dataset", 0, _("Dataset (in-place updated, unless --read-only)"), + &m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE) + .SetPositional() + .SetRequired(); + AddArg("external", 0, _("Delete external overviews"), &m_readOnly) + .AddHiddenAlias("ro") + .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY); +} + +/************************************************************************/ +/* GDALRasterOverviewAlgorithmDelete::RunImpl() */ +/************************************************************************/ + +bool GDALRasterOverviewAlgorithmDelete::RunImpl(GDALProgressFunc pfnProgress, + void *pProgressData) +{ + auto poDS = m_dataset.GetDatasetRef(); + CPLAssert(poDS); + + return GDALBuildOverviews(GDALDataset::ToHandle(poDS), "NONE", 0, nullptr, + 0, nullptr, pfnProgress, + pProgressData) == CE_None; +} + +//! @endcond diff --git a/apps/gdalalg_raster_overview_delete.h b/apps/gdalalg_raster_overview_delete.h new file mode 100644 index 000000000000..b075f197fa47 --- /dev/null +++ b/apps/gdalalg_raster_overview_delete.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: gdal "raster overview delete" subcommand + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2025, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef GDALALG_RASTER_OVERVIEW_DELETE_INCLUDED +#define GDALALG_RASTER_OVERVIEW_DELETE_INCLUDED + +#include "gdalalgorithm.h" + +//! @cond Doxygen_Suppress + +/************************************************************************/ +/* GDALRasterOverviewAlgorithmDelete */ +/************************************************************************/ + +class GDALRasterOverviewAlgorithmDelete final : public GDALAlgorithm +{ + public: + static constexpr const char *NAME = "delete"; + static constexpr const char *DESCRIPTION = "Deleting overviews."; + static constexpr const char *HELP_URL = + "/programs/gdal_raster_overview_delete.html"; + + static std::vector GetAliases() + { + return {}; + } + + GDALRasterOverviewAlgorithmDelete(); + + private: + bool RunImpl(GDALProgressFunc, void *) override; + + GDALArgDatasetValue m_dataset{}; + std::vector m_openOptions{}; + std::vector m_inputFormats{}; + bool m_readOnly = false; +}; + +//! @endcond + +#endif diff --git a/apps/gdalalg_raster_reproject.cpp b/apps/gdalalg_raster_reproject.cpp index b3eb388ec35b..5e09c07bd204 100644 --- a/apps/gdalalg_raster_reproject.cpp +++ b/apps/gdalalg_raster_reproject.cpp @@ -36,9 +36,10 @@ GDALRasterReprojectAlgorithm::GDALRasterReprojectAlgorithm(bool standaloneStep) .SetIsCRSArg() .AddHiddenAlias("t_srs"); AddArg("resampling", 'r', _("Resampling method"), &m_resampling) - .SetChoices("near", "bilinear", "cubic", "cubicspline", "lanczos", + .SetChoices("nearest", "bilinear", "cubic", "cubicspline", "lanczos", "average", "rms", "mode", "min", "max", "med", "q1", "q3", - "sum"); + "sum") + .SetHiddenChoices("near"); auto &resArg = AddArg("resolution", 0, diff --git a/autotest/cpp/test_gdal_algorithm.cpp b/autotest/cpp/test_gdal_algorithm.cpp index d8ca927ae20c..edaf85cd3756 100644 --- a/autotest/cpp/test_gdal_algorithm.cpp +++ b/autotest/cpp/test_gdal_algorithm.cpp @@ -1130,7 +1130,9 @@ TEST_F(test_gdal_algorithm, string_choices) MyAlgorithm() { - AddArg("val", 0, "", &m_val).SetChoices("foo", "bar"); + AddArg("val", 0, "", &m_val) + .SetChoices("foo", "bar") + .SetHiddenChoices("baz"); } }; @@ -1142,6 +1144,14 @@ TEST_F(test_gdal_algorithm, string_choices) EXPECT_STREQ(alg.m_val.c_str(), "foo"); } + { + MyAlgorithm alg; + alg.GetUsageForCLI(false); + + EXPECT_TRUE(alg.ParseCommandLineArguments({"--val=baz"})); + EXPECT_STREQ(alg.m_val.c_str(), "baz"); + } + { MyAlgorithm alg; CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); diff --git a/autotest/utilities/test_gdal.py b/autotest/utilities/test_gdal.py index ef1f50efaf18..92ec6d58741c 100755 --- a/autotest/utilities/test_gdal.py +++ b/autotest/utilities/test_gdal.py @@ -393,4 +393,4 @@ def test_gdal_completion_pipeline(gdal_path, subcommand): out = gdaltest.runexternal( f"{gdal_path} completion gdal {subcommand} pipeline read foo ! reproject --resampling" ).split(" ") - assert "near" in out + assert "nearest" in out diff --git a/autotest/utilities/test_gdalalg_raster_overview.py b/autotest/utilities/test_gdalalg_raster_overview.py new file mode 100755 index 000000000000..e193469d5154 --- /dev/null +++ b/autotest/utilities/test_gdalalg_raster_overview.py @@ -0,0 +1,150 @@ +#!/usr/bin/env pytest +# -*- coding: utf-8 -*- +############################################################################### +# Project: GDAL/OGR Test Suite +# Purpose: 'gdal raster iveview' testing +# Author: Even Rouault +# +############################################################################### +# Copyright (c) 2025, Even Rouault +# +# SPDX-License-Identifier: MIT +############################################################################### + +import pytest + +from osgeo import gdal + + +def get_overview_alg(): + reg = gdal.GetGlobalAlgorithmRegistry() + raster = reg.InstantiateAlg("raster") + return raster.InstantiateSubAlgorithm("overview") + + +def get_overview_add_alg(): + return get_overview_alg().InstantiateSubAlgorithm("add") + + +def get_overview_delete_alg(): + return get_overview_alg().InstantiateSubAlgorithm("delete") + + +def test_gdalalg_overview_invalid_arguments(): + + add = get_overview_add_alg() + with pytest.raises(Exception): + add.GetArg("levels").Set([1]) + + add = get_overview_add_alg() + with pytest.raises(Exception): + add.GetArg("min-size").Set(0) + + +def test_gdalalg_overview_explicit_level(): + + ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM") + + add = get_overview_add_alg() + add.GetArg("dataset").Get().SetDataset(ds) + add.GetArg("levels").Set([2]) + assert add.Run() + + assert ds.GetRasterBand(1).Checksum() == 4672 + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1087 + + +def test_gdalalg_overview_minsize_and_resampling(): + + ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM") + + add = get_overview_add_alg() + add.GetArg("dataset").Get().SetDataset(ds) + add.GetArg("resampling").Set("average") + add.GetArg("min-size").Set(10) + assert add.Run() + + assert ds.GetRasterBand(1).Checksum() == 4672 + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1152 + + +def test_gdalalg_overview_reuse_resampling_and_levels(tmp_vsimem): + + tmp_filename = str(tmp_vsimem / "tmp.tif") + ds = gdal.Translate(tmp_filename, "../gcore/data/byte.tif") + + add = get_overview_add_alg() + add.GetArg("dataset").Get().SetDataset(ds) + add.GetArg("resampling").Set("average") + add.GetArg("min-size").Set(10) + assert add.Run() + + assert ds.GetRasterBand(1).Checksum() == 4672 + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + assert ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("RESAMPLING") == "AVERAGE" + assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1152 + + ds.GetRasterBand(1).GetOverview(0).Fill(0) + + add = get_overview_add_alg() + add.GetArg("dataset").Get().SetDataset(ds) + assert add.Run() + + assert ds.GetRasterBand(1).Checksum() == 4672 + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + assert ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("RESAMPLING") == "AVERAGE" + assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 1152 + + +def test_gdalalg_overview_in_plae(tmp_vsimem): + + tmp_filename = str(tmp_vsimem / "tmp.tif") + gdal.Translate(tmp_filename, "../gcore/data/byte.tif") + + add = get_overview_add_alg() + assert add.ParseRunAndFinalize([tmp_filename, "--levels=2"]) + + assert gdal.VSIStatL(tmp_filename + ".ovr") is None + + with gdal.Open(tmp_filename) as ds: + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + + +def test_gdalalg_overview_external(tmp_vsimem): + + tmp_filename = str(tmp_vsimem / "tmp.tif") + gdal.Translate(tmp_filename, "../gcore/data/byte.tif") + + add = get_overview_add_alg() + assert add.ParseRunAndFinalize([tmp_filename, "--levels=2", "--external"]) + + assert gdal.VSIStatL(tmp_filename + ".ovr") is not None + + with gdal.Open(tmp_filename) as ds: + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + + delete = get_overview_delete_alg() + assert delete.ParseRunAndFinalize([tmp_filename, "--external"]) + + assert gdal.VSIStatL(tmp_filename + ".ovr") is None + + +def test_gdalalg_overview_delete(): + + ds = gdal.Translate("", "../gcore/data/byte.tif", format="MEM") + + add = get_overview_add_alg() + add.GetArg("dataset").Get().SetDataset(ds) + add.GetArg("resampling").Set("average") + add.GetArg("min-size").Set(10) + assert add.Run() + + assert ds.GetRasterBand(1).GetOverviewCount() == 1 + + delete = get_overview_delete_alg() + delete.GetArg("dataset").Get().SetDataset(ds) + assert delete.Run() + + assert ds.GetRasterBand(1).GetOverviewCount() == 0 diff --git a/doc/source/conf.py b/doc/source/conf.py index 625663f20718..b6dd101312ff 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -243,6 +243,20 @@ [author_evenr], 1, ), + ( + "programs/gdal_raster_overview_add", + "gdal-raster-overview-add", + "Add overviews to a raster dataset", + [author_evenr], + 1, + ), + ( + "programs/gdal_raster_overview_delete", + "gdal-raster-overview-delete", + "Delete overviews of a raster dataset", + [author_evenr], + 1, + ), ( "programs/gdal_raster_pipeline", "gdal-raster-pipeline", diff --git a/doc/source/programs/gdal_raster.rst b/doc/source/programs/gdal_raster.rst index 67cd7bd12fe1..2c83336282b6 100644 --- a/doc/source/programs/gdal_raster.rst +++ b/doc/source/programs/gdal_raster.rst @@ -22,6 +22,7 @@ Synopsis - buildvrt: Build a virtual dataset (VRT). - convert: Convert a raster dataset. - info: Return information on a raster dataset. + - overview: Manage overviews of a raster dataset. - pipeline: Process a raster dataset. - reproject: Reproject a raster dataset. @@ -31,6 +32,7 @@ Available sub-commands - :ref:`gdal_raster_buildvrt_subcommand` - :ref:`gdal_raster_info_subcommand` - :ref:`gdal_raster_convert_subcommand` +- :ref:`gdal_raster_overview_subcommand` - :ref:`gdal_raster_pipeline_subcommand` - :ref:`gdal_raster_reproject_subcommand` diff --git a/doc/source/programs/gdal_raster_overview.rst b/doc/source/programs/gdal_raster_overview.rst new file mode 100644 index 000000000000..ec7133c0caeb --- /dev/null +++ b/doc/source/programs/gdal_raster_overview.rst @@ -0,0 +1,29 @@ +.. _gdal_raster_overview_subcommand: + +================================================================================ +"gdal raster overview" sub-command +================================================================================ + +.. versionadded:: 3.11 + +.. only:: html + + Manage overviews of a raster dataset + +.. Index:: gdal raster overview + +Synopsis +-------- + +.. code-block:: + + Usage: gdal raster + where is one of: + - add: Adding overviews. + - delete: Deleting overviews. + +Available sub-commands +---------------------- + +- :ref:`gdal_raster_overview_add_subcommand` +- :ref:`gdal_raster_overview_delete_subcommand` diff --git a/doc/source/programs/gdal_raster_overview_add.rst b/doc/source/programs/gdal_raster_overview_add.rst new file mode 100644 index 000000000000..0868d269ac35 --- /dev/null +++ b/doc/source/programs/gdal_raster_overview_add.rst @@ -0,0 +1,154 @@ +.. _gdal_raster_overview_add_subcommand: + +================================================================================ +"gdal raster overview add" sub-command +================================================================================ + +.. versionadded:: 3.11 + +.. only:: html + + Add overviews to a raster dataset + +.. Index:: gdal raster overview add + +Synopsis +-------- + +.. code-block:: + + Usage: gdal raster overview add [OPTIONS] + + Adding overviews. + + Positional arguments: + --dataset Dataset (in-place updated, unless --external) [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: + --external Add external overviews + -r, --resampling Resampling method. RESAMPLING=nearest|average|cubic|cubicspline|lanczos|bilinear|gauss|average_magphase|rms|mode + --levels Levels / decimation factors [may be repeated] + --min-size Maximum width or height of the smallest overview level. + + Advanced Options: + --oo, --open-option Open options [may be repeated] + +Description +----------- + +:program:`gdal raster overview add` can be used to build or rebuild overview images for +most supported file formats with one of several downsampling algorithms. + +.. option:: --dataset + + Dataset name, to be in-place updated by default (unless :option:`--external` is specified). Required. + +.. option:: --external + + Create external ``.ovr`` overviews as GeoTIFF files. + +.. option:: --resampling {nearest|average|cubic|cubicspline|lanczos|bilinear|gauss|average_magphase|rms|mode} + + Select a resampling algorithm. The default is ``nearest``, which is generally not + appropriate if sub-pixel accuracy is desired. + + When refreshing existing TIFF overviews, the previously + used method, as noted in the RESAMPLING metadata item of the overview, will + be used if :option:`-r` is not specified. + + The available methods are: + + ``nearest`` applies a nearest neighbour (simple sampling) resampler. + + ``average`` computes the average of all non-NODATA contributing pixels. This is a weighted average taking into account properly the weight of source pixels not contributing fully to the target pixel. + + ``bilinear`` applies a bilinear convolution kernel. + + ``cubic`` applies a cubic convolution kernel. + + ``cubicspline`` applies a B-Spline convolution kernel. + + ``lanczos`` applies a Lanczos windowed sinc convolution kernel. + + ``gauss`` applies a Gaussian kernel before computing the overview, + which can lead to better results than simple averaging in e.g case of sharp edges + with high contrast or noisy patterns. The advised level values should be 2, 4, 8, ... + so that a 3x3 resampling Gaussian kernel is selected. + + ``average_magphase`` averages complex data in mag/phase space. + + ``rms`` computes the root mean squared / quadratic mean of all non-NODATA contributing pixels + + ``mode`` selects the value which appears most often of all the sampled points. + +.. option:: --levels + + A list of overview levels to build. Each overview level must be an integer + value greater or equal to 2. + + When explicit levels are not specified, + + - If there are already existing overviews, the corresponding levels will be + used to refresh them if no explicit levels are specified. + + - Otherwise, appropriate overview power-of-two factors will be selected + until the smallest overview is smaller than the value of the + :option:`--min-size` switch. + +.. option:: --min-size + + Maximum width or height of the smallest overview level. Only taken into + account if explicit levels are not specified. Defaults to 256. + + +Examples +-------- + +.. example:: + :title: Create overviews, embedded in the supplied TIFF file, with automatic computation of levels + + .. code-block:: bash + + gdal raster overview add -r average abc.tif + +.. example:: + :title: Create overviews, embedded in the supplied TIFF file + + .. code-block:: bash + + gdal raster overview add -r average --levels=2,4,8,16 abc.tif + +.. example:: + :title: Create an external compressed GeoTIFF overview file from the ERDAS .IMG file + + .. code-block:: bash + + gdal raster overview add --external --levels=2,4,8,16 --config COMPRESS_OVERVIEW=DEFLATE erdas.img + +.. example:: + :title: Create an external JPEG-compressed GeoTIFF overview file from a 3-band RGB dataset + + If the dataset is a writable GeoTIFF, you also need to add the :option:`--external` option to + force the generation of external overview. + + .. code-block:: bash + + gdal raster overview add --config COMPRESS_OVERVIEW=JPEG --config PHOTOMETRIC_OVERVIEW=YCBCR + --config INTERLEAVE_OVERVIEW=PIXEL rgb_dataset.ext 2 4 8 16 + +.. example:: + :title: Create overviews for a specific subdataset + + For example, one of potentially many raster layers in a GeoPackage (the "filename" parameter must be driver prefix, filename and subdataset name, like e.g. shown by gdalinfo): + + .. code-block:: bash + + gdal raster overview add GPKG:file.gpkg:layer diff --git a/doc/source/programs/gdal_raster_overview_delete.rst b/doc/source/programs/gdal_raster_overview_delete.rst new file mode 100644 index 000000000000..62844fa6c086 --- /dev/null +++ b/doc/source/programs/gdal_raster_overview_delete.rst @@ -0,0 +1,71 @@ +.. _gdal_raster_overview_delete_subcommand: + +================================================================================ +"gdal raster overview delete" sub-command +================================================================================ + +.. versionadded:: 3.11 + +.. only:: html + + Delete overviews of a raster dataset + +.. Index:: gdal raster overview delete + +Synopsis +-------- + +.. code-block:: + + Usage: gdal raster overview delete [OPTIONS] + + Deleting overviews. + + Positional arguments: + --dataset Dataset (in-place updated, unless --external) [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: + --external Remove external overviews + + Advanced Options: + --oo, --open-option Open options [may be repeated] + + +Description +----------- + +:program:`gdal raster overview delete` can be used to delete all existing overviews +of a dataset. + +.. note:: + + For most file formats (including GeoTIFF or GeoPackage), the space + previously occupied by the removed overviews may not be reclaimed. + It might be needed for use :ref:`gdal_raster_convert_subcommand` to create + a new compact dataset. + +.. option:: --dataset + + Dataset name, to be in-place updated by default (unless :option:`--external` is specified). Required. + +.. option:: --external + + Remove external ``.ovr`` overviews. + +Examples +-------- + +.. example:: + :title: Delete overviews of a GeoTIFF file. + + .. code-block:: bash + + gdal raster overview delete my.tif diff --git a/doc/source/programs/gdal_raster_reproject.rst b/doc/source/programs/gdal_raster_reproject.rst index a123dc5d6788..6a27f2dde6de 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=near|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 --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/index.rst b/doc/source/programs/index.rst index c2160d4b7ff5..4e3dea737f80 100644 --- a/doc/source/programs/index.rst +++ b/doc/source/programs/index.rst @@ -33,6 +33,9 @@ single :program:`gdal` program that accepts commands and subcommands. gdal_raster_info gdal_raster_convert gdal_raster_edit + gdal_raster_overview + gdal_raster_overview_add + gdal_raster_overview_delete gdal_raster_pipeline gdal_raster_reproject gdal_vector @@ -50,6 +53,9 @@ single :program:`gdal` program that accepts commands and subcommands. - :ref:`gdal_raster_info_subcommand`: Get information on a raster dataset - :ref:`gdal_raster_convert_subcommand`: Convert a raster dataset - :ref:`gdal_raster_edit_subcommand`: Edit in place a raster dataset + - :ref:`gdal_raster_overview_subcommand`: Manage overviews of a raster dataset + - :ref:`gdal_raster_overview_add_subcommand`: Add overviews to a raster dataset + - :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_vector_command`: Entry point for vector commands diff --git a/gcore/gdalalgorithm.cpp b/gcore/gdalalgorithm.cpp index 9f138cf5683a..fff0d219812e 100644 --- a/gcore/gdalalgorithm.cpp +++ b/gcore/gdalalgorithm.cpp @@ -889,8 +889,12 @@ bool GDALAlgorithm::ParseArgument( case GAAT_STRING: { const auto &choices = arg->GetChoices(); - if (!choices.empty() && std::find(choices.begin(), choices.end(), - value) == choices.end()) + const auto &hiddenChoices = arg->GetHiddenChoices(); + if (!choices.empty() && + std::find(choices.begin(), choices.end(), value) == + choices.end() && + std::find(hiddenChoices.begin(), hiddenChoices.end(), value) == + hiddenChoices.end()) { std::string expected; for (const auto &choice : choices) @@ -966,11 +970,14 @@ bool GDALAlgorithm::ParseArgument( auto &valueVector = std::get>(inConstructionValues[arg]); const auto &choices = arg->GetChoices(); + const auto &hiddenChoices = arg->GetHiddenChoices(); for (const char *v : aosTokens) { if (!choices.empty() && std::find(choices.begin(), choices.end(), v) == - choices.end()) + choices.end() && + std::find(hiddenChoices.begin(), hiddenChoices.end(), + value) == hiddenChoices.end()) { std::string expected; for (const auto &choice : choices) @@ -1505,6 +1512,13 @@ bool GDALAlgorithm::ProcessDatasetArg(GDALAlgorithmArg *arg, if ((arg == outputArg || !outputArg) && update) flags |= GDAL_OF_UPDATE | GDAL_OF_VERBOSE_ERROR; + const auto readOnlyArg = algForOutput->GetArg(GDAL_ARG_NAME_READ_ONLY); + const bool readOnly = + (readOnlyArg && readOnlyArg->GetType() == GAAT_BOOLEAN && + readOnlyArg->Get()); + if (readOnly) + flags &= ~GDAL_OF_UPDATE; + CPLStringList aosOpenOptions; CPLStringList aosAllowedDrivers; if (arg->GetName() == GDAL_ARG_NAME_INPUT) diff --git a/gcore/gdalalgorithm.h b/gcore/gdalalgorithm.h index a42024f971d5..3823c8cced4c 100644 --- a/gcore/gdalalgorithm.h +++ b/gcore/gdalalgorithm.h @@ -300,6 +300,9 @@ constexpr const char *GDAL_ARG_NAME_OUTPUT = "output"; /** Name of the argument for update. */ constexpr const char *GDAL_ARG_NAME_UPDATE = "update"; +/** Name of the argument for read-only. */ +constexpr const char *GDAL_ARG_NAME_READ_ONLY = "read-only"; + /************************************************************************/ /* GDALArgDatasetValue */ /************************************************************************/ @@ -665,6 +668,25 @@ class CPL_DLL GDALAlgorithmArgDecl final return *this; } + //! @cond Doxygen_Suppress + GDALAlgorithmArgDecl &SetHiddenChoices() + { + return *this; + } + + //! @endcond + + /** Declares the, hidden, allowed values (as strings) for the argument. + * Only honored for GAAT_STRING and GAAT_STRING_LIST types. + */ + template + GDALAlgorithmArgDecl &SetHiddenChoices(T &&first, U &&...rest) + { + m_hiddenChoices.push_back(std::forward(first)); + SetHiddenChoices(std::forward(rest)...); + return *this; + } + /** Declare that the argument must not be mentioned in CLI usage. * For example, "output-value" for "gdal raster info", which is only * meant when the algorithm is used from a non-CLI context. @@ -808,6 +830,14 @@ class CPL_DLL GDALAlgorithmArgDecl final return m_choices; } + /** Return the allowed hidden values (as strings) for the argument. + * Only honored for GAAT_STRING and GAAT_STRING_LIST types. + */ + inline const std::vector &GetHiddenChoices() const + { + return m_hiddenChoices; + } + /** Return whether the argument is required. Defaults to false. */ inline bool IsRequired() const @@ -1002,6 +1032,7 @@ class CPL_DLL GDALAlgorithmArgDecl final std::vector m_aliases{}; std::vector m_hiddenAliases{}; std::vector m_choices{}; + std::vector m_hiddenChoices{}; std::variant, std::vector, std::vector> m_defaultValue{}; @@ -1127,6 +1158,12 @@ class CPL_DLL GDALAlgorithmArg /* non-final */ return m_decl.GetChoices(); } + /** Alias for GDALAlgorithmArgDecl::GetHiddenChoices() */ + inline const std::vector &GetHiddenChoices() const + { + return m_decl.GetHiddenChoices(); + } + /** Return auto completion choices, if a auto completion function has been * registered. */ @@ -1537,6 +1574,15 @@ class CPL_DLL GDALInConstructionAlgorithmArg final : public GDALAlgorithmArg return *this; } + /** Alias for GDALAlgorithmArgDecl::SetHiddenChoices() */ + template + GDALInConstructionAlgorithmArg &SetHiddenChoices(T &&first, U &&...rest) + { + m_decl.SetHiddenChoices(std::forward(first), + std::forward(rest)...); + return *this; + } + /** Alias for GDALAlgorithmArgDecl::SetHiddenForCLI() */ GDALInConstructionAlgorithmArg &SetHiddenForCLI(bool hiddenForCLI = true) {