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