/src/gdal/apps/gdalalg_raster_zonal_stats.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: gdal "raster zonal-stats" subcommand |
5 | | * Author: Dan Baston |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2025, ISciences LLC |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_raster_zonal_stats.h" |
14 | | |
15 | | #include "gdal_alg.h" |
16 | | #include "gdal_priv.h" |
17 | | #include "gdal_utils.h" |
18 | | #include "ogrsf_frmts.h" |
19 | | |
20 | | //! @cond Doxygen_Suppress |
21 | | |
22 | | #ifndef _ |
23 | 0 | #define _(x) (x) |
24 | | #endif |
25 | | |
26 | | /************************************************************************/ |
27 | | /* GDALRasterZonalStatsAlgorithm::GDALRasterZonalStatsAlgorithm() */ |
28 | | /************************************************************************/ |
29 | | |
30 | | GDALRasterZonalStatsAlgorithm::GDALRasterZonalStatsAlgorithm(bool bStandalone) |
31 | 0 | : GDALPipelineStepAlgorithm( |
32 | 0 | NAME, DESCRIPTION, HELP_URL, |
33 | 0 | ConstructorOptions() |
34 | 0 | .SetStandaloneStep(bStandalone) |
35 | 0 | .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE)) |
36 | 0 | { |
37 | 0 | AddRasterInputArgs(false, false); |
38 | 0 | if (bStandalone) |
39 | 0 | { |
40 | 0 | AddVectorOutputArgs(false, false); |
41 | 0 | } |
42 | |
|
43 | 0 | constexpr const char *ZONES_BAND_OR_LAYER = "BAND_OR_LAYER"; |
44 | |
|
45 | 0 | AddBandArg(&m_bands); |
46 | 0 | AddArg("zones", 0, _("Dataset containing zone definitions"), &m_zones) |
47 | 0 | .SetRequired(); |
48 | 0 | AddArg("zones-band", 0, _("Band from which zones should be read"), |
49 | 0 | &m_zonesBand) |
50 | 0 | .SetMutualExclusionGroup(ZONES_BAND_OR_LAYER); |
51 | 0 | AddArg("zones-layer", 0, _("Layer from which zones should be read"), |
52 | 0 | &m_zonesLayer) |
53 | 0 | .SetMutualExclusionGroup(ZONES_BAND_OR_LAYER); |
54 | 0 | AddArg("weights", 0, _("Weighting raster dataset"), &m_weights); |
55 | 0 | AddArg("weights-band", 0, _("Band from which weights should be read"), |
56 | 0 | &m_weightsBand) |
57 | 0 | .SetDefault(1); |
58 | 0 | AddArg( |
59 | 0 | "pixels", 0, |
60 | 0 | _("Method to determine which pixels are included in stat calculation."), |
61 | 0 | &m_pixels) |
62 | 0 | .SetChoices("default", "fractional", "all-touched"); |
63 | 0 | AddArg("stat", 0, _("Statistic(s) to compute for each zone"), &m_stats) |
64 | 0 | .SetRequired() |
65 | 0 | .SetMinValueIncluded(1) |
66 | 0 | .SetChoices("center_x", "center_y", "count", "coverage", "frac", "max", |
67 | 0 | "max_center_x", "max_center_y", "mean", "median", "min", |
68 | 0 | "minority", "min_center_x", "min_center_y", "mode", "stdev", |
69 | 0 | "sum", "unique", "values", "variance", "variety", |
70 | 0 | "weighted_mean", "weighted_stdev", "weighted_sum", |
71 | 0 | "weighted_variance", "weights"); |
72 | 0 | AddArg("include-field", 0, |
73 | 0 | _("Fields from polygon zones to include in output"), |
74 | 0 | &m_includeFields); |
75 | 0 | AddArg("strategy", 0, |
76 | 0 | _("For polygon zones, whether to iterate over input features or " |
77 | 0 | "raster chunks"), |
78 | 0 | &m_strategy) |
79 | 0 | .SetChoices("feature", "raster") |
80 | 0 | .SetDefault("feature"); |
81 | 0 | AddMemorySizeArg(&m_memoryBytes, &m_memoryStr, "chunk-size", |
82 | 0 | _("Maximum size of raster chunks read into memory")); |
83 | 0 | AddProgressArg(); |
84 | 0 | } |
85 | | |
86 | | /************************************************************************/ |
87 | | /* GDALRasterZonalStatsAlgorithm::RunStep() */ |
88 | | /************************************************************************/ |
89 | | |
90 | | bool GDALRasterZonalStatsAlgorithm::RunImpl(GDALProgressFunc pfnProgress, |
91 | | void *pProgressData) |
92 | 0 | { |
93 | 0 | GDALPipelineStepRunContext stepCtxt; |
94 | 0 | stepCtxt.m_pfnProgress = pfnProgress; |
95 | 0 | stepCtxt.m_pProgressData = pProgressData; |
96 | 0 | return RunPreStepPipelineValidations() && RunStep(stepCtxt); |
97 | 0 | } |
98 | | |
99 | | template <typename T> |
100 | | static std::string Join(const T &vec, const std::string &separator) |
101 | 0 | { |
102 | 0 | std::stringstream ss; |
103 | 0 | bool first = true; |
104 | 0 | for (const auto &val : vec) |
105 | 0 | { |
106 | 0 | static_assert(!std::is_floating_point_v<decltype(val)>, |
107 | 0 | "Precision would be lost."); |
108 | |
|
109 | 0 | if (first) |
110 | 0 | { |
111 | 0 | first = false; |
112 | 0 | } |
113 | 0 | else |
114 | 0 | { |
115 | 0 | ss << separator; |
116 | 0 | } |
117 | 0 | ss << val; |
118 | 0 | } |
119 | 0 | return ss.str(); |
120 | 0 | } Unexecuted instantiation: gdalalg_raster_zonal_stats.cpp:std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > Join<std::__1::vector<int, std::__1::allocator<int> > >(std::__1::vector<int, std::__1::allocator<int> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Unexecuted instantiation: gdalalg_raster_zonal_stats.cpp:std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > Join<std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) |
121 | | |
122 | | bool GDALRasterZonalStatsAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
123 | 0 | { |
124 | 0 | GDALDataset *zones = m_zones.GetDatasetRef(); |
125 | | |
126 | | /// Prepare the output dataset. |
127 | | /// This section copy-pasted from gdal raster as-features. |
128 | | /// Avoid duplication. |
129 | 0 | GDALDataset *poDstDS = m_outputDataset.GetDatasetRef(); |
130 | 0 | std::unique_ptr<GDALDataset> poRetDS; |
131 | 0 | if (!poDstDS) |
132 | 0 | { |
133 | 0 | std::string outputFilename = m_outputDataset.GetName(); |
134 | 0 | if (m_standaloneStep) |
135 | 0 | { |
136 | 0 | if (m_format.empty()) |
137 | 0 | { |
138 | 0 | const auto aosFormats = |
139 | 0 | CPLStringList(GDALGetOutputDriversForDatasetName( |
140 | 0 | m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR, |
141 | 0 | /* bSingleMatch = */ true, |
142 | 0 | /* bWarn = */ true)); |
143 | 0 | if (aosFormats.size() != 1) |
144 | 0 | { |
145 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
146 | 0 | "Cannot guess driver for %s", |
147 | 0 | m_outputDataset.GetName().c_str()); |
148 | 0 | return false; |
149 | 0 | } |
150 | 0 | m_format = aosFormats[0]; |
151 | 0 | } |
152 | 0 | } |
153 | 0 | else |
154 | 0 | { |
155 | 0 | m_format = "MEM"; |
156 | 0 | } |
157 | | |
158 | 0 | auto poDriver = |
159 | 0 | GetGDALDriverManager()->GetDriverByName(m_format.c_str()); |
160 | 0 | if (!poDriver) |
161 | 0 | { |
162 | | // shouldn't happen given checks done in GDALAlgorithm |
163 | 0 | ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s", |
164 | 0 | m_format.c_str()); |
165 | 0 | return false; |
166 | 0 | } |
167 | | |
168 | 0 | poRetDS.reset( |
169 | 0 | poDriver->Create(outputFilename.c_str(), 0, 0, 0, GDT_Unknown, |
170 | 0 | CPLStringList(m_creationOptions).List())); |
171 | 0 | if (!poRetDS) |
172 | 0 | return false; |
173 | | |
174 | 0 | poDstDS = poRetDS.get(); |
175 | 0 | } |
176 | | /// End prep of output dataset. |
177 | | |
178 | 0 | GDALDataset *src = m_inputDataset.front().GetDatasetRef(); |
179 | |
|
180 | 0 | CPLStringList aosOptions; |
181 | 0 | if (!m_bands.empty()) |
182 | 0 | { |
183 | 0 | aosOptions.AddNameValue("BANDS", Join(m_bands, ",").c_str()); |
184 | 0 | } |
185 | 0 | if (!m_includeFields.empty()) |
186 | 0 | { |
187 | 0 | aosOptions.AddNameValue("INCLUDE_FIELDS", |
188 | 0 | Join(m_includeFields, ",").c_str()); |
189 | 0 | } |
190 | 0 | aosOptions.AddNameValue("PIXEL_INTERSECTION", m_pixels.c_str()); |
191 | 0 | if (m_memoryBytes != 0) |
192 | 0 | { |
193 | 0 | aosOptions.AddNameValue("RASTER_CHUNK_SIZE_BYTES", |
194 | 0 | std::to_string(m_memoryBytes).c_str()); |
195 | 0 | } |
196 | 0 | aosOptions.AddNameValue("STATS", Join(m_stats, ",").c_str()); |
197 | 0 | aosOptions.AddNameValue("STRATEGY", (m_strategy + "_SEQUENTIAL").c_str()); |
198 | 0 | if (m_weightsBand != 0) |
199 | 0 | { |
200 | 0 | aosOptions.AddNameValue("WEIGHTS_BAND", |
201 | 0 | std::to_string(m_weightsBand).c_str()); |
202 | 0 | } |
203 | 0 | if (m_zonesBand != 0) |
204 | 0 | { |
205 | 0 | aosOptions.AddNameValue("ZONES_BAND", |
206 | 0 | std::to_string(m_zonesBand).c_str()); |
207 | 0 | } |
208 | 0 | if (!m_zonesLayer.empty()) |
209 | 0 | { |
210 | 0 | aosOptions.AddNameValue("ZONES_LAYER", m_zonesLayer.c_str()); |
211 | 0 | } |
212 | 0 | for (const std::string &lco : m_layerCreationOptions) |
213 | 0 | { |
214 | 0 | aosOptions.AddString(std::string("LCO_").append(lco)); |
215 | 0 | } |
216 | |
|
217 | 0 | if (poRetDS) |
218 | 0 | { |
219 | 0 | m_outputDataset.Set(std::move(poRetDS)); |
220 | 0 | } |
221 | |
|
222 | 0 | return GDALZonalStats(src, m_weights.GetDatasetRef(), zones, poDstDS, |
223 | 0 | aosOptions.List(), ctxt.m_pfnProgress, |
224 | 0 | ctxt.m_pProgressData) == CE_None; |
225 | 0 | } |
226 | | |
227 | | GDALRasterZonalStatsAlgorithmStandalone:: |
228 | 0 | ~GDALRasterZonalStatsAlgorithmStandalone() = default; |
229 | | |
230 | | //! @endcond |