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