/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 | | 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 | | 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 | | const OGRFeatureDefn *GetLayerDefn() const override |
96 | 0 | { |
97 | 0 | return m_poFeatureDefn.get(); |
98 | 0 | } |
99 | | |
100 | | void ResetReading() override |
101 | 0 | { |
102 | 0 | m_nextFID = 1; |
103 | 0 | GDALVectorPipelineOutputLayer::ResetReading(); |
104 | 0 | } |
105 | | |
106 | | OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent, |
107 | | bool bForce) override |
108 | 0 | { |
109 | 0 | return m_srcLayer.GetExtent(iGeomField, psExtent, bForce); |
110 | 0 | } |
111 | | |
112 | | int TestCapability(const char *pszCap) const override |
113 | 0 | { |
114 | 0 | if (EQUAL(pszCap, OLCCurveGeometries) || |
115 | 0 | EQUAL(pszCap, OLCMeasuredGeometries) || |
116 | 0 | EQUAL(pszCap, OLCZGeometries) || EQUAL(pszCap, OLCFastGetExtent) || |
117 | 0 | EQUAL(pszCap, OLCStringsAsUTF8)) |
118 | 0 | { |
119 | 0 | return m_srcLayer.TestCapability(pszCap); |
120 | 0 | } |
121 | 0 | return false; |
122 | 0 | } |
123 | | }; |
124 | | |
125 | | /************************************************************************/ |
126 | | /* GDALVectorExplodeCollectionsAlgorithmLayer() */ |
127 | | /************************************************************************/ |
128 | | |
129 | | GDALVectorExplodeCollectionsAlgorithmLayer:: |
130 | | GDALVectorExplodeCollectionsAlgorithmLayer( |
131 | | OGRLayer &oSrcLayer, |
132 | | const GDALVectorExplodeCollectionsAlgorithm::Options &opts) |
133 | 0 | : GDALVectorPipelineOutputLayer(oSrcLayer), m_opts(opts), |
134 | 0 | m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone()) |
135 | 0 | { |
136 | 0 | SetDescription(oSrcLayer.GetDescription()); |
137 | 0 | SetMetadata(oSrcLayer.GetMetadata()); |
138 | |
|
139 | 0 | if (!m_opts.m_geomField.empty()) |
140 | 0 | { |
141 | 0 | const int nIdx = oSrcLayer.GetLayerDefn()->GetGeomFieldIndex( |
142 | 0 | m_opts.m_geomField.c_str()); |
143 | 0 | if (nIdx >= 0) |
144 | 0 | m_iGeomIdx = nIdx; |
145 | 0 | else |
146 | 0 | m_iGeomIdx = INT_MAX; |
147 | 0 | } |
148 | |
|
149 | 0 | for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i) |
150 | 0 | { |
151 | 0 | if (IsSelectedGeomField(i)) |
152 | 0 | { |
153 | 0 | const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i); |
154 | 0 | poGeomFieldDefn->SetType( |
155 | 0 | !m_opts.m_type.empty() |
156 | 0 | ? m_opts.m_eType |
157 | 0 | : OGR_GT_GetSingle(poGeomFieldDefn->GetType())); |
158 | 0 | } |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | /************************************************************************/ |
163 | | /* TranslateFeature() */ |
164 | | /************************************************************************/ |
165 | | |
166 | | void GDALVectorExplodeCollectionsAlgorithmLayer::TranslateFeature( |
167 | | std::unique_ptr<OGRFeature> poSrcFeature, |
168 | | std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) |
169 | 0 | { |
170 | 0 | std::list<std::pair<std::unique_ptr<OGRFeature>, int>> apoTmpFeatures; |
171 | 0 | apoTmpFeatures.emplace_back(std::move(poSrcFeature), 0); |
172 | 0 | const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount(); |
173 | 0 | while (!apoTmpFeatures.empty()) |
174 | 0 | { |
175 | 0 | auto [poCurFeature, nextGeomIndex] = std::move(apoTmpFeatures.front()); |
176 | 0 | auto insertionPoint = apoTmpFeatures.erase(apoTmpFeatures.begin()); |
177 | 0 | bool bInsertionDone = false; |
178 | 0 | for (int i = nextGeomIndex; i < nGeomFieldCount; ++i) |
179 | 0 | { |
180 | 0 | auto poGeom = poCurFeature->GetGeomFieldRef(i); |
181 | 0 | if (poGeom && !poGeom->IsEmpty() && |
182 | 0 | OGR_GT_IsSubClassOf(poGeom->getGeometryType(), |
183 | 0 | wkbGeometryCollection) && |
184 | 0 | IsSelectedGeomField(i)) |
185 | 0 | { |
186 | 0 | const auto poGeomFieldDefn = |
187 | 0 | m_poFeatureDefn->GetGeomFieldDefn(i); |
188 | 0 | bInsertionDone = true; |
189 | 0 | const auto eTargetType = |
190 | 0 | !m_opts.m_type.empty() |
191 | 0 | ? m_opts.m_eType |
192 | 0 | : OGR_GT_GetSingle(poGeomFieldDefn->GetType()); |
193 | 0 | auto poColl = std::unique_ptr<OGRGeometryCollection>( |
194 | 0 | poCurFeature->StealGeometry(i)->toGeometryCollection()); |
195 | 0 | bool bTmpFeaturesInserted = false; |
196 | 0 | for (const auto *poSubGeomRef : poColl.get()) |
197 | 0 | { |
198 | 0 | auto poNewFeature = |
199 | 0 | std::unique_ptr<OGRFeature>(poCurFeature->Clone()); |
200 | 0 | auto poNewGeom = |
201 | 0 | std::unique_ptr<OGRGeometry>(poSubGeomRef->clone()); |
202 | 0 | if (poNewGeom->getGeometryType() != eTargetType) |
203 | 0 | poNewGeom = OGRGeometryFactory::forceTo( |
204 | 0 | std::move(poNewGeom), eTargetType); |
205 | 0 | if (m_opts.m_skip && !m_opts.m_type.empty() && |
206 | 0 | (!poNewGeom || |
207 | 0 | (wkbFlatten(eTargetType) != wkbUnknown && |
208 | 0 | poNewGeom->getGeometryType() != eTargetType))) |
209 | 0 | { |
210 | | // skip |
211 | 0 | } |
212 | 0 | else |
213 | 0 | { |
214 | 0 | poNewGeom->assignSpatialReference( |
215 | 0 | poGeomFieldDefn->GetSpatialRef()); |
216 | 0 | poNewFeature->SetGeomFieldDirectly(i, |
217 | 0 | poNewGeom.release()); |
218 | |
|
219 | 0 | if (!m_opts.m_geomField.empty() || |
220 | 0 | i == nGeomFieldCount - 1) |
221 | 0 | { |
222 | 0 | poNewFeature->SetFDefnUnsafe(m_poFeatureDefn.get()); |
223 | 0 | poNewFeature->SetFID(m_nextFID); |
224 | 0 | ++m_nextFID; |
225 | 0 | apoOutFeatures.push_back(std::move(poNewFeature)); |
226 | 0 | } |
227 | 0 | else |
228 | 0 | { |
229 | 0 | bTmpFeaturesInserted = true; |
230 | 0 | apoTmpFeatures.insert( |
231 | 0 | insertionPoint, |
232 | 0 | std::pair<std::unique_ptr<OGRFeature>, int>( |
233 | 0 | std::move(poNewFeature), |
234 | 0 | nextGeomIndex + 1)); |
235 | 0 | } |
236 | 0 | } |
237 | 0 | } |
238 | |
|
239 | 0 | if (bTmpFeaturesInserted) |
240 | 0 | break; |
241 | 0 | } |
242 | 0 | else if (poGeom) |
243 | 0 | { |
244 | 0 | const auto poGeomFieldDefn = |
245 | 0 | m_poFeatureDefn->GetGeomFieldDefn(i); |
246 | 0 | poGeom->assignSpatialReference( |
247 | 0 | poGeomFieldDefn->GetSpatialRef()); |
248 | 0 | } |
249 | 0 | } |
250 | 0 | if (!bInsertionDone) |
251 | 0 | { |
252 | 0 | poCurFeature->SetFDefnUnsafe(m_poFeatureDefn.get()); |
253 | 0 | poCurFeature->SetFID(m_nextFID); |
254 | 0 | ++m_nextFID; |
255 | 0 | apoOutFeatures.push_back(std::move(poCurFeature)); |
256 | 0 | } |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | } // namespace |
261 | | |
262 | | /************************************************************************/ |
263 | | /* GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer() */ |
264 | | /************************************************************************/ |
265 | | |
266 | | std::unique_ptr<OGRLayerWithTranslateFeature> |
267 | | GDALVectorExplodeCollectionsAlgorithm::CreateAlgLayer(OGRLayer &srcLayer) |
268 | 0 | { |
269 | 0 | return std::make_unique<GDALVectorExplodeCollectionsAlgorithmLayer>( |
270 | 0 | srcLayer, m_opts); |
271 | 0 | } |
272 | | |
273 | | /************************************************************************/ |
274 | | /* GDALVectorExplodeCollectionsAlgorithm::RunStep() */ |
275 | | /************************************************************************/ |
276 | | |
277 | | bool GDALVectorExplodeCollectionsAlgorithm::RunStep( |
278 | | GDALPipelineStepRunContext &ctxt) |
279 | 0 | { |
280 | 0 | if (!m_opts.m_type.empty()) |
281 | 0 | { |
282 | 0 | m_opts.m_eType = OGRFromOGCGeomType(m_opts.m_type.c_str()); |
283 | 0 | if (wkbFlatten(m_opts.m_eType) == wkbUnknown && |
284 | 0 | !STARTS_WITH_CI(m_opts.m_type.c_str(), "GEOMETRY")) |
285 | 0 | { |
286 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
287 | 0 | "Invalid geometry type '%s'", m_opts.m_type.c_str()); |
288 | 0 | return false; |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | 0 | return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt); |
293 | 0 | } |
294 | | |
295 | | GDALVectorExplodeCollectionsAlgorithmStandalone:: |
296 | 0 | ~GDALVectorExplodeCollectionsAlgorithmStandalone() = default; |
297 | | |
298 | | //! @endcond |