Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster polygonize" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_raster_polygonize.h"
14 : #include "gdalalg_vector_write.h"
15 :
16 : #include "cpl_conv.h"
17 : #include "gdal_priv.h"
18 : #include "gdal_alg.h"
19 : #include "ogrsf_frmts.h"
20 :
21 : //! @cond Doxygen_Suppress
22 :
23 : #ifndef _
24 : #define _(x) (x)
25 : #endif
26 :
27 : /************************************************************************/
28 : /* GDALRasterPolygonizeAlgorithm::GDALRasterPolygonizeAlgorithm() */
29 : /************************************************************************/
30 :
31 50 : GDALRasterPolygonizeAlgorithm::GDALRasterPolygonizeAlgorithm(
32 50 : bool standaloneStep)
33 : : GDALPipelineStepAlgorithm(
34 : NAME, DESCRIPTION, HELP_URL,
35 0 : ConstructorOptions()
36 50 : .SetStandaloneStep(standaloneStep)
37 50 : .SetAddUpsertArgument(false)
38 50 : .SetAddSkipErrorsArgument(false)
39 100 : .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
40 : {
41 50 : m_outputLayerName = "polygonize";
42 :
43 50 : AddProgressArg();
44 50 : if (standaloneStep)
45 : {
46 29 : AddRasterInputArgs(false, false);
47 29 : AddVectorOutputArgs(false, false);
48 : }
49 : else
50 : {
51 21 : AddRasterHiddenInputDatasetArg();
52 21 : AddOutputLayerNameArg(/* hiddenForCLI = */ false,
53 : /* shortNameOutputLayerAllowed = */ false);
54 : }
55 :
56 : // gdal_polygonize specific options
57 50 : AddBandArg(&m_band).SetDefault(m_band);
58 : AddArg("attribute-name", 0, _("Name of the field with the pixel value"),
59 100 : &m_attributeName)
60 50 : .SetDefault(m_attributeName);
61 :
62 : AddArg("connect-diagonal-pixels", 'c',
63 100 : _("Consider diagonal pixels as connected"), &m_connectDiagonalPixels)
64 50 : .SetDefault(m_connectDiagonalPixels);
65 :
66 100 : AddArg("commit-interval", 0, _("Commit interval"), &m_commitInterval)
67 50 : .SetHidden();
68 50 : }
69 :
70 3 : bool GDALRasterPolygonizeAlgorithm::CanHandleNextStep(
71 : GDALPipelineStepAlgorithm *poNextStep) const
72 : {
73 5 : return poNextStep->GetName() == GDALVectorWriteAlgorithm::NAME &&
74 5 : poNextStep->GetOutputFormat() != "stream";
75 : }
76 :
77 : /************************************************************************/
78 : /* GDALRasterPolygonizeAlgorithm::RunImpl() */
79 : /************************************************************************/
80 :
81 18 : bool GDALRasterPolygonizeAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
82 : void *pProgressData)
83 : {
84 18 : GDALPipelineStepRunContext stepCtxt;
85 18 : stepCtxt.m_pfnProgress = pfnProgress;
86 18 : stepCtxt.m_pProgressData = pProgressData;
87 18 : return RunPreStepPipelineValidations() && RunStep(stepCtxt);
88 : }
89 :
90 : /************************************************************************/
91 : /* GDALRasterPolygonizeAlgorithm::RunStep() */
92 : /************************************************************************/
93 :
94 22 : bool GDALRasterPolygonizeAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
95 : {
96 22 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
97 22 : CPLAssert(poSrcDS);
98 :
99 22 : auto poWriteStep = ctxt.m_poNextUsableStep ? ctxt.m_poNextUsableStep : this;
100 :
101 22 : GDALDataset *poDstDS = poWriteStep->GetOutputDataset().GetDatasetRef();
102 22 : std::unique_ptr<GDALDataset> poRetDS;
103 44 : std::string outputFilename = poWriteStep->GetOutputDataset().GetName();
104 22 : GDALDriver *poDstDriver = nullptr;
105 22 : bool bTemporaryFile = false;
106 22 : if (!poDstDS)
107 : {
108 17 : auto poDriverManager = GetGDALDriverManager();
109 17 : std::string format = poWriteStep->GetOutputFormat();
110 17 : if (m_standaloneStep || (ctxt.m_poNextUsableStep && format.empty()))
111 : {
112 14 : if (format.empty())
113 : {
114 : const auto aosFormats =
115 : CPLStringList(GDALGetOutputDriversForDatasetName(
116 : outputFilename.c_str(), GDAL_OF_VECTOR,
117 : /* bSingleMatch = */ true,
118 9 : /* bWarn = */ true));
119 9 : if (aosFormats.size() != 1)
120 : {
121 1 : ReportError(CE_Failure, CPLE_AppDefined,
122 : "Cannot guess driver for %s",
123 : outputFilename.c_str());
124 1 : return false;
125 : }
126 8 : format = aosFormats[0];
127 : }
128 : }
129 3 : else if (!ctxt.m_poNextUsableStep)
130 : {
131 2 : poDstDriver = poDriverManager->GetDriverByName("GPKG");
132 2 : if (poDstDriver)
133 : {
134 2 : bTemporaryFile = true;
135 : outputFilename =
136 2 : CPLGenerateTempFilenameSafe("_polygonize") + ".gpkg";
137 2 : format = "GPKG";
138 : }
139 : else
140 0 : format = "MEM";
141 : }
142 :
143 16 : if (!poDstDriver)
144 14 : poDstDriver = poDriverManager->GetDriverByName(format.c_str());
145 16 : if (!poDstDriver)
146 : {
147 0 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
148 : format.c_str());
149 0 : return false;
150 : }
151 :
152 16 : poRetDS.reset(poDstDriver->Create(
153 : outputFilename.c_str(), 0, 0, 0, GDT_Unknown,
154 32 : CPLStringList(poWriteStep->GetCreationOptions()).List()));
155 16 : if (!poRetDS)
156 1 : return false;
157 :
158 15 : if (bTemporaryFile)
159 2 : poRetDS->MarkSuppressOnClose();
160 :
161 15 : poDstDS = poRetDS.get();
162 : }
163 : else
164 : {
165 5 : poDstDriver = poDstDS->GetDriver();
166 : }
167 :
168 40 : std::string outputLayerName = poWriteStep->GetOutputLayerName();
169 20 : if (poDstDriver && EQUAL(poDstDriver->GetDescription(), "ESRI Shapefile") &&
170 45 : EQUAL(CPLGetExtensionSafe(poDstDS->GetDescription()).c_str(), "shp") &&
171 5 : poDstDS->GetLayerCount() <= 1)
172 : {
173 5 : outputLayerName = CPLGetBasenameSafe(poDstDS->GetDescription());
174 : }
175 :
176 20 : auto poDstLayer = poDstDS->GetLayerByName(outputLayerName.c_str());
177 20 : if (poDstLayer)
178 : {
179 4 : if (poWriteStep->GetOverwriteLayer())
180 : {
181 1 : int iLayer = -1;
182 1 : const int nLayerCount = poDstDS->GetLayerCount();
183 1 : for (iLayer = 0; iLayer < nLayerCount; iLayer++)
184 : {
185 1 : if (poDstDS->GetLayer(iLayer) == poDstLayer)
186 1 : break;
187 : }
188 :
189 1 : if (iLayer < nLayerCount)
190 : {
191 1 : if (poDstDS->DeleteLayer(iLayer) != OGRERR_NONE)
192 : {
193 0 : ReportError(CE_Failure, CPLE_AppDefined,
194 : "Cannot delete layer '%s'",
195 : outputLayerName.c_str());
196 0 : return false;
197 : }
198 : }
199 1 : poDstLayer = nullptr;
200 : }
201 3 : else if (!poWriteStep->GetAppendLayer())
202 : {
203 1 : ReportError(CE_Failure, CPLE_AppDefined,
204 : "Layer '%s' already exists. Specify the "
205 : "--%s option to overwrite it, or --%s "
206 : "to append to it.",
207 : outputLayerName.c_str(), GDAL_ARG_NAME_OVERWRITE_LAYER,
208 : GDAL_ARG_NAME_APPEND);
209 1 : return false;
210 : }
211 : }
212 16 : else if (poWriteStep->GetAppendLayer() || poWriteStep->GetOverwriteLayer())
213 : {
214 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot find layer '%s'",
215 : outputLayerName.c_str());
216 1 : return false;
217 : }
218 :
219 18 : auto poSrcBand = poSrcDS->GetRasterBand(m_band);
220 18 : const auto eDT = poSrcBand->GetRasterDataType();
221 :
222 18 : if (!poDstLayer)
223 : {
224 16 : poDstLayer = poDstDS->CreateLayer(
225 16 : outputLayerName.c_str(), poSrcDS->GetSpatialRef(), wkbPolygon,
226 32 : CPLStringList(poWriteStep->GetLayerCreationOptions()).List());
227 16 : if (!poDstLayer)
228 : {
229 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot create layer '%s'",
230 : outputLayerName.c_str());
231 1 : return false;
232 : }
233 :
234 : OGRFieldDefn oFieldDefn(m_attributeName.c_str(),
235 15 : !GDALDataTypeIsInteger(eDT) ? OFTReal
236 13 : : eDT == GDT_Int64 || eDT == GDT_UInt64
237 27 : ? OFTInteger64
238 29 : : OFTInteger);
239 15 : if (poDstLayer->CreateField(&oFieldDefn) != OGRERR_NONE)
240 : {
241 0 : ReportError(CE_Failure, CPLE_AppDefined,
242 : "Cannot create field '%s' in layer '%s'",
243 : m_attributeName.c_str(), outputLayerName.c_str());
244 0 : return false;
245 : }
246 : }
247 :
248 : const int iPixValField =
249 17 : poDstLayer->GetLayerDefn()->GetFieldIndex(m_attributeName.c_str());
250 17 : if (iPixValField < 0)
251 : {
252 1 : ReportError(CE_Failure, CPLE_AppDefined,
253 : "Cannot find field '%s' in layer '%s'",
254 : m_attributeName.c_str(), outputLayerName.c_str());
255 1 : return false;
256 : }
257 :
258 16 : CPLStringList aosPolygonizeOptions;
259 16 : if (m_connectDiagonalPixels)
260 : {
261 1 : aosPolygonizeOptions.SetNameValue("8CONNECTED", "8");
262 : }
263 16 : if (m_commitInterval)
264 : {
265 : aosPolygonizeOptions.SetNameValue("COMMIT_INTERVAL",
266 2 : CPLSPrintf("%d", m_commitInterval));
267 : }
268 :
269 : bool ret;
270 16 : if (GDALDataTypeIsInteger(eDT))
271 : {
272 30 : ret = GDALPolygonize(GDALRasterBand::ToHandle(poSrcBand),
273 15 : GDALRasterBand::ToHandle(poSrcBand->GetMaskBand()),
274 : OGRLayer::ToHandle(poDstLayer), iPixValField,
275 : aosPolygonizeOptions.List(), ctxt.m_pfnProgress,
276 : ctxt.m_pProgressData) == CE_None;
277 : }
278 : else
279 : {
280 1 : ret =
281 2 : GDALFPolygonize(GDALRasterBand::ToHandle(poSrcBand),
282 1 : GDALRasterBand::ToHandle(poSrcBand->GetMaskBand()),
283 : OGRLayer::ToHandle(poDstLayer), iPixValField,
284 : aosPolygonizeOptions.List(), ctxt.m_pfnProgress,
285 : ctxt.m_pProgressData) == CE_None;
286 : }
287 :
288 16 : if (ret && poRetDS)
289 : {
290 14 : if (bTemporaryFile)
291 : {
292 2 : ret = poRetDS->FlushCache() == CE_None;
293 : #if !defined(__APPLE__)
294 : // For some unknown reason, unlinking the file on MacOSX
295 : // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
296 2 : VSIUnlink(outputFilename.c_str());
297 : #endif
298 : }
299 :
300 14 : m_outputDataset.Set(std::move(poRetDS));
301 : }
302 :
303 16 : return ret;
304 : }
305 :
306 : GDALRasterPolygonizeAlgorithmStandalone::
307 : ~GDALRasterPolygonizeAlgorithmStandalone() = default;
308 :
309 : //! @endcond
|