Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "gdal vector explode-collections"
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_vector_explode_collections.h"
14 :
15 : #include "gdal_priv.h"
16 : #include "ogrsf_frmts.h"
17 :
18 : #include <list>
19 : #include <utility>
20 :
21 : //! @cond Doxygen_Suppress
22 :
23 : #ifndef _
24 : #define _(x) (x)
25 : #endif
26 :
27 : /************************************************************************/
28 : /* GDALVectorExplodeCollectionsAlgorithm() */
29 : /************************************************************************/
30 :
31 88 : GDALVectorExplodeCollectionsAlgorithm::GDALVectorExplodeCollectionsAlgorithm(
32 88 : bool standaloneStep)
33 : : GDALVectorGeomAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL,
34 88 : standaloneStep, m_opts)
35 : {
36 176 : AddArg("geometry-type", 0, _("Geometry type"), &m_opts.m_type)
37 : .SetAutoCompleteFunction(
38 2 : [](const std::string ¤tValue)
39 : {
40 2 : std::vector<std::string> oRet;
41 18 : for (const char *type :
42 : {"GEOMETRY", "POINT", "LINESTRING", "POLYGON",
43 : "CIRCULARSTRING", "COMPOUNDCURVE", "CURVEPOLYGON",
44 20 : "POLYHEDRALSURFACE", "TIN"})
45 : {
46 27 : if (currentValue.empty() ||
47 9 : STARTS_WITH(type, currentValue.c_str()))
48 : {
49 10 : oRet.push_back(type);
50 10 : oRet.push_back(std::string(type).append("Z"));
51 10 : oRet.push_back(std::string(type).append("M"));
52 10 : oRet.push_back(std::string(type).append("ZM"));
53 : }
54 : }
55 2 : return oRet;
56 88 : });
57 :
58 : AddArg("skip-on-type-mismatch", 0,
59 : _("Skip feature when change of feature geometry type failed"),
60 88 : &m_opts.m_skip);
61 88 : }
62 :
63 : namespace
64 : {
65 :
66 : /************************************************************************/
67 : /* GDALVectorExplodeCollectionsAlgorithmLayer */
68 : /************************************************************************/
69 :
70 : class GDALVectorExplodeCollectionsAlgorithmLayer final
71 : : public GDALVectorPipelineOutputLayer
72 : {
73 : private:
74 : const GDALVectorExplodeCollectionsAlgorithm::Options m_opts;
75 : int m_iGeomIdx = -1;
76 : const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
77 : GIntBig m_nextFID = 1;
78 :
79 : CPL_DISALLOW_COPY_ASSIGN(GDALVectorExplodeCollectionsAlgorithmLayer)
80 :
81 : void TranslateFeature(
82 : std::unique_ptr<OGRFeature> poSrcFeature,
83 : std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override;
84 :
85 31 : bool IsSelectedGeomField(int idx) const
86 : {
87 31 : return m_iGeomIdx < 0 || idx == m_iGeomIdx;
88 : }
89 :
90 : public:
91 : GDALVectorExplodeCollectionsAlgorithmLayer(
92 : OGRLayer &oSrcLayer,
93 : const GDALVectorExplodeCollectionsAlgorithm::Options &opts);
94 :
95 221 : const OGRFeatureDefn *GetLayerDefn() const override
96 : {
97 221 : return m_poFeatureDefn.get();
98 : }
99 :
100 121 : void ResetReading() override
101 : {
102 121 : m_nextFID = 1;
103 121 : GDALVectorPipelineOutputLayer::ResetReading();
104 121 : }
105 :
106 5 : OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
107 : bool bForce) override
108 : {
109 5 : return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
110 : }
111 :
112 39 : int TestCapability(const char *pszCap) const override
113 : {
114 39 : if (EQUAL(pszCap, OLCCurveGeometries) ||
115 35 : EQUAL(pszCap, OLCMeasuredGeometries) ||
116 31 : EQUAL(pszCap, OLCZGeometries) || EQUAL(pszCap, OLCFastGetExtent) ||
117 26 : EQUAL(pszCap, OLCStringsAsUTF8))
118 : {
119 27 : return m_srcLayer.TestCapability(pszCap);
120 : }
121 12 : return false;
122 : }
123 : };
124 :
125 : /************************************************************************/
126 : /* GDALVectorExplodeCollectionsAlgorithmLayer() */
127 : /************************************************************************/
128 :
129 8 : GDALVectorExplodeCollectionsAlgorithmLayer::
130 : GDALVectorExplodeCollectionsAlgorithmLayer(
131 : OGRLayer &oSrcLayer,
132 8 : const GDALVectorExplodeCollectionsAlgorithm::Options &opts)
133 : : GDALVectorPipelineOutputLayer(oSrcLayer), m_opts(opts),
134 8 : m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
135 : {
136 8 : SetDescription(oSrcLayer.GetDescription());
137 8 : SetMetadata(oSrcLayer.GetMetadata());
138 :
139 8 : if (!m_opts.m_geomField.empty())
140 : {
141 4 : const int nIdx = oSrcLayer.GetLayerDefn()->GetGeomFieldIndex(
142 2 : m_opts.m_geomField.c_str());
143 2 : if (nIdx >= 0)
144 2 : m_iGeomIdx = nIdx;
145 : else
146 0 : m_iGeomIdx = INT_MAX;
147 : }
148 :
149 19 : for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
150 : {
151 11 : if (IsSelectedGeomField(i))
152 : {
153 9 : const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
154 9 : poGeomFieldDefn->SetType(
155 9 : !m_opts.m_type.empty()
156 : ? m_opts.m_eType
157 7 : : OGR_GT_GetSingle(poGeomFieldDefn->GetType()));
158 : }
159 : }
160 8 : }
161 :
162 : /************************************************************************/
163 : /* TranslateFeature() */
164 : /************************************************************************/
165 :
166 413 : void GDALVectorExplodeCollectionsAlgorithmLayer::TranslateFeature(
167 : std::unique_ptr<OGRFeature> poSrcFeature,
168 : std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures)
169 : {
170 826 : std::list<std::pair<std::unique_ptr<OGRFeature>, int>> apoTmpFeatures;
171 413 : apoTmpFeatures.emplace_back(std::move(poSrcFeature), 0);
172 413 : const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
173 832 : while (!apoTmpFeatures.empty())
174 : {
175 838 : auto [poCurFeature, nextGeomIndex] = std::move(apoTmpFeatures.front());
176 419 : auto insertionPoint = apoTmpFeatures.erase(apoTmpFeatures.begin());
177 419 : bool bInsertionDone = false;
178 839 : for (int i = nextGeomIndex; i < nGeomFieldCount; ++i)
179 : {
180 423 : auto poGeom = poCurFeature->GetGeomFieldRef(i);
181 422 : if (poGeom && !poGeom->IsEmpty() &&
182 422 : OGR_GT_IsSubClassOf(poGeom->getGeometryType(),
183 845 : wkbGeometryCollection) &&
184 20 : IsSelectedGeomField(i))
185 : {
186 : const auto poGeomFieldDefn =
187 16 : m_poFeatureDefn->GetGeomFieldDefn(i);
188 16 : bInsertionDone = true;
189 : const auto eTargetType =
190 16 : !m_opts.m_type.empty()
191 16 : ? m_opts.m_eType
192 14 : : OGR_GT_GetSingle(poGeomFieldDefn->GetType());
193 : auto poColl = std::unique_ptr<OGRGeometryCollection>(
194 16 : poCurFeature->StealGeometry(i)->toGeometryCollection());
195 16 : bool bTmpFeaturesInserted = false;
196 49 : for (const auto *poSubGeomRef : poColl.get())
197 : {
198 : auto poNewFeature =
199 66 : std::unique_ptr<OGRFeature>(poCurFeature->Clone());
200 : auto poNewGeom =
201 66 : std::unique_ptr<OGRGeometry>(poSubGeomRef->clone());
202 33 : if (poNewGeom->getGeometryType() != eTargetType)
203 6 : poNewGeom = OGRGeometryFactory::forceTo(
204 6 : std::move(poNewGeom), eTargetType);
205 36 : if (m_opts.m_skip && !m_opts.m_type.empty() &&
206 3 : (!poNewGeom ||
207 3 : (wkbFlatten(eTargetType) != wkbUnknown &&
208 3 : poNewGeom->getGeometryType() != eTargetType)))
209 : {
210 : // skip
211 : }
212 : else
213 : {
214 64 : poNewGeom->assignSpatialReference(
215 32 : poGeomFieldDefn->GetSpatialRef());
216 32 : poNewFeature->SetGeomFieldDirectly(i,
217 : poNewGeom.release());
218 :
219 56 : if (!m_opts.m_geomField.empty() ||
220 24 : i == nGeomFieldCount - 1)
221 : {
222 26 : poNewFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
223 26 : poNewFeature->SetFID(m_nextFID);
224 26 : ++m_nextFID;
225 26 : apoOutFeatures.push_back(std::move(poNewFeature));
226 : }
227 : else
228 : {
229 6 : bTmpFeaturesInserted = true;
230 : apoTmpFeatures.insert(
231 : insertionPoint,
232 6 : std::pair<std::unique_ptr<OGRFeature>, int>(
233 6 : std::move(poNewFeature),
234 12 : nextGeomIndex + 1));
235 : }
236 : }
237 : }
238 :
239 16 : if (bTmpFeaturesInserted)
240 3 : break;
241 : }
242 407 : else if (poGeom)
243 : {
244 : const auto poGeomFieldDefn =
245 406 : m_poFeatureDefn->GetGeomFieldDefn(i);
246 406 : poGeom->assignSpatialReference(
247 406 : poGeomFieldDefn->GetSpatialRef());
248 : }
249 : }
250 419 : if (!bInsertionDone)
251 : {
252 403 : poCurFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
253 403 : poCurFeature->SetFID(m_nextFID);
254 403 : ++m_nextFID;
255 403 : apoOutFeatures.push_back(std::move(poCurFeature));
256 : }
257 : }
258 413 : }
259 :
260 : } // namespace
261 :
262 : /************************************************************************/
263 : /* GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer() */
264 : /************************************************************************/
265 :
266 : std::unique_ptr<OGRLayerWithTranslateFeature>
267 8 : GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer(OGRLayer &srcLayer)
268 : {
269 8 : return std::make_unique<GDALVectorExplodeCollectionsAlgorithmLayer>(
270 8 : srcLayer, m_opts);
271 : }
272 :
273 : /************************************************************************/
274 : /* GDALVectorExplodeCollectionsAlgorithm::RunStep() */
275 : /************************************************************************/
276 :
277 9 : bool GDALVectorExplodeCollectionsAlgorithm::RunStep(
278 : GDALPipelineStepRunContext &ctxt)
279 : {
280 9 : if (!m_opts.m_type.empty())
281 : {
282 3 : m_opts.m_eType = OGRFromOGCGeomType(m_opts.m_type.c_str());
283 4 : if (wkbFlatten(m_opts.m_eType) == wkbUnknown &&
284 1 : !STARTS_WITH_CI(m_opts.m_type.c_str(), "GEOMETRY"))
285 : {
286 1 : ReportError(CE_Failure, CPLE_AppDefined,
287 : "Invalid geometry type '%s'", m_opts.m_type.c_str());
288 1 : return false;
289 : }
290 : }
291 :
292 8 : return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt);
293 : }
294 :
295 : GDALVectorExplodeCollectionsAlgorithmStandalone::
296 : ~GDALVectorExplodeCollectionsAlgorithmStandalone() = default;
297 :
298 : //! @endcond
|