/src/gdal/apps/gdalalg_vector_rename_layer.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: "rename-layer" step of "vector pipeline" |
5 | | * Author: Even Rouault <even dot rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_vector_rename_layer.h" |
14 | | |
15 | | //! @cond Doxygen_Suppress |
16 | | |
17 | | #include <map> |
18 | | |
19 | | #include "cpl_string.h" |
20 | | |
21 | | #ifndef _ |
22 | 0 | #define _(x) (x) |
23 | | #endif |
24 | | |
25 | | /************************************************************************/ |
26 | | /* GDALVectorRenameLayerAlgorithm::GDALVectorRenameLayerAlgorithm() */ |
27 | | /************************************************************************/ |
28 | | |
29 | | GDALVectorRenameLayerAlgorithm::GDALVectorRenameLayerAlgorithm( |
30 | | bool standaloneStep) |
31 | 0 | : GDALVectorPipelineStepAlgorithm( |
32 | 0 | NAME, DESCRIPTION, HELP_URL, |
33 | 0 | ConstructorOptions() |
34 | 0 | .SetStandaloneStep(standaloneStep) |
35 | 0 | .SetAddInputLayerNameArgument(false) |
36 | 0 | .SetOutputLayerNameAvailableInPipelineStep(true)) |
37 | 0 | { |
38 | 0 | AddLayerNameArg(&m_inputLayerName); |
39 | 0 | if (!standaloneStep) |
40 | 0 | { |
41 | 0 | AddOutputLayerNameArg(/* hiddenForCLI = */ false, |
42 | 0 | /* shortNameOutputLayerAllowed = */ false); |
43 | 0 | } |
44 | 0 | AddArg("ascii", 0, _("Force names to ASCII character"), &m_ascii); |
45 | 0 | AddArg("lower-case", 0, |
46 | 0 | _("Force names to lower case (only on ASCII characters)"), |
47 | 0 | &m_lowerCase); |
48 | 0 | AddArg("filename-compatible", 0, _("Force names to be usable as filenames"), |
49 | 0 | &m_filenameCompatible); |
50 | 0 | AddArg("reserved-characters", 0, _("Reserved character(s) to be removed"), |
51 | 0 | &m_reservedChars); |
52 | 0 | AddArg("replacement-character", 0, |
53 | 0 | _("Replacement character when ASCII conversion not possible"), |
54 | 0 | &m_replacementChar) |
55 | 0 | .SetMaxCharCount(1); |
56 | 0 | AddArg("max-length", 0, _("Maximum length of layer names"), &m_maxLength) |
57 | 0 | .SetMinValueIncluded(1); |
58 | |
|
59 | 0 | AddValidationAction( |
60 | 0 | [this]() |
61 | 0 | { |
62 | 0 | if (!m_inputLayerName.empty() && m_outputLayerName.empty()) |
63 | 0 | { |
64 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
65 | 0 | "Argument output-layer must be specified when " |
66 | 0 | "input-layer is specified"); |
67 | 0 | return false; |
68 | 0 | } |
69 | | |
70 | 0 | if (!m_inputDataset.empty() && m_inputDataset[0].GetDatasetRef()) |
71 | 0 | { |
72 | 0 | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
73 | 0 | if (!m_inputLayerName.empty() && |
74 | 0 | poSrcDS->GetLayerByName(m_inputLayerName.c_str()) == |
75 | 0 | nullptr) |
76 | 0 | { |
77 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
78 | 0 | "Input layer '%s' does not exist", |
79 | 0 | m_inputLayerName.c_str()); |
80 | 0 | return false; |
81 | 0 | } |
82 | | |
83 | 0 | if (!m_outputLayerName.empty() && m_inputLayerName.empty() && |
84 | 0 | poSrcDS->GetLayerCount() >= 2) |
85 | 0 | { |
86 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
87 | 0 | "Argument input-layer must be specified when " |
88 | 0 | "output-layer is specified and there is more " |
89 | 0 | "than one layer"); |
90 | 0 | return false; |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | 0 | return true; |
95 | 0 | }); |
96 | 0 | } |
97 | | |
98 | | namespace |
99 | | { |
100 | | |
101 | | /************************************************************************/ |
102 | | /* GDALVectorRenameLayerAlgorithmLayer */ |
103 | | /************************************************************************/ |
104 | | |
105 | | class GDALVectorRenameLayerAlgorithmLayer final |
106 | | : public GDALVectorPipelineOutputLayer |
107 | | { |
108 | | private: |
109 | | const OGRFeatureDefnRefCountedPtr m_poFeatureDefn; |
110 | | |
111 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorRenameLayerAlgorithmLayer) |
112 | | |
113 | | bool TranslateFeature( |
114 | | std::unique_ptr<OGRFeature> poSrcFeature, |
115 | | std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override |
116 | 0 | { |
117 | 0 | poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get()); |
118 | 0 | apoOutFeatures.push_back(std::move(poSrcFeature)); |
119 | 0 | return true; |
120 | 0 | } |
121 | | |
122 | | public: |
123 | | explicit GDALVectorRenameLayerAlgorithmLayer( |
124 | | OGRLayer &oSrcLayer, const std::string &osOutputLayerName) |
125 | 0 | : GDALVectorPipelineOutputLayer(oSrcLayer), |
126 | 0 | m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone()) |
127 | 0 | { |
128 | 0 | m_poFeatureDefn->SetName(osOutputLayerName.c_str()); |
129 | 0 | const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount(); |
130 | 0 | const auto poSrcLayerDefn = oSrcLayer.GetLayerDefn(); |
131 | 0 | for (int i = 0; i < nGeomFieldCount; ++i) |
132 | 0 | { |
133 | 0 | m_poFeatureDefn->GetGeomFieldDefn(i)->SetSpatialRef( |
134 | 0 | poSrcLayerDefn->GetGeomFieldDefn(i)->GetSpatialRef()); |
135 | 0 | } |
136 | 0 | SetDescription(m_poFeatureDefn->GetName()); |
137 | 0 | SetMetadata(oSrcLayer.GetMetadata()); |
138 | 0 | } |
139 | | |
140 | | const OGRFeatureDefn *GetLayerDefn() const override |
141 | 0 | { |
142 | 0 | return m_poFeatureDefn.get(); |
143 | 0 | } |
144 | | |
145 | | GIntBig GetFeatureCount(int bForce) override |
146 | 0 | { |
147 | 0 | return m_srcLayer.GetFeatureCount(bForce); |
148 | 0 | } |
149 | | |
150 | | OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent, |
151 | | bool bForce) override |
152 | 0 | { |
153 | 0 | return m_srcLayer.GetExtent(iGeomField, psExtent, bForce); |
154 | 0 | } |
155 | | |
156 | | OGRErr SetIgnoredFields(CSLConstList papszFields) override |
157 | 0 | { |
158 | 0 | return m_srcLayer.SetIgnoredFields(papszFields); |
159 | 0 | } |
160 | | |
161 | | OGRErr SetAttributeFilter(const char *pszAttributeFilter) override |
162 | 0 | { |
163 | 0 | OGRLayer::SetAttributeFilter(pszAttributeFilter); |
164 | 0 | return m_srcLayer.SetAttributeFilter(pszAttributeFilter); |
165 | 0 | } |
166 | | |
167 | | OGRGeometry *GetSpatialFilter() override |
168 | 0 | { |
169 | 0 | return m_srcLayer.GetSpatialFilter(); |
170 | 0 | } |
171 | | |
172 | | OGRErr ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom) override |
173 | 0 | { |
174 | 0 | return m_srcLayer.SetSpatialFilter(iGeomField, poGeom); |
175 | 0 | } |
176 | | |
177 | | OGRFeature *GetFeature(GIntBig nFID) override |
178 | 0 | { |
179 | 0 | auto poSrcFeature = |
180 | 0 | std::unique_ptr<OGRFeature>(m_srcLayer.GetFeature(nFID)); |
181 | 0 | if (!poSrcFeature) |
182 | 0 | return nullptr; |
183 | 0 | poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get()); |
184 | 0 | return poSrcFeature.release(); |
185 | 0 | } |
186 | | |
187 | | int TestCapability(const char *pszCap) const override |
188 | 0 | { |
189 | 0 | return m_srcLayer.TestCapability(pszCap); |
190 | 0 | } |
191 | | }; |
192 | | |
193 | | /************************************************************************/ |
194 | | /* GDALVectorRenameLayerAlgorithmDataset */ |
195 | | /************************************************************************/ |
196 | | |
197 | | class GDALVectorRenameLayerAlgorithmDataset final |
198 | | : public GDALVectorPipelineOutputDataset |
199 | | { |
200 | | public: |
201 | | GDALVectorRenameLayerAlgorithmDataset( |
202 | | GDALDataset &oSrcDS, const std::vector<std::string> &aosNewLayerNames) |
203 | 0 | : GDALVectorPipelineOutputDataset(oSrcDS) |
204 | 0 | { |
205 | 0 | const int nLayerCount = oSrcDS.GetLayerCount(); |
206 | 0 | CPLAssert(aosNewLayerNames.size() == static_cast<size_t>(nLayerCount)); |
207 | 0 | for (int i = 0; i < nLayerCount; ++i) |
208 | 0 | { |
209 | 0 | m_mapOldLayerNameToNew[oSrcDS.GetLayer(i)->GetName()] = |
210 | 0 | aosNewLayerNames[i]; |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | | const GDALRelationship * |
215 | | GetRelationship(const std::string &name) const override; |
216 | | |
217 | | private: |
218 | | std::map<std::string, std::string> m_mapOldLayerNameToNew{}; |
219 | | mutable std::map<std::string, std::unique_ptr<GDALRelationship>> |
220 | | m_relationships{}; |
221 | | }; |
222 | | |
223 | | /************************************************************************/ |
224 | | /* GetRelationship() */ |
225 | | /************************************************************************/ |
226 | | |
227 | | const GDALRelationship *GDALVectorRenameLayerAlgorithmDataset::GetRelationship( |
228 | | const std::string &name) const |
229 | 0 | { |
230 | 0 | const auto oIterRelationships = m_relationships.find(name); |
231 | 0 | if (oIterRelationships != m_relationships.end()) |
232 | 0 | return oIterRelationships->second.get(); |
233 | | |
234 | 0 | const GDALRelationship *poSrcRelationShip = m_srcDS.GetRelationship(name); |
235 | 0 | if (!poSrcRelationShip) |
236 | 0 | return nullptr; |
237 | 0 | const auto oIterLeftTableName = |
238 | 0 | m_mapOldLayerNameToNew.find(poSrcRelationShip->GetLeftTableName()); |
239 | 0 | const auto oIterRightTableName = |
240 | 0 | m_mapOldLayerNameToNew.find(poSrcRelationShip->GetRightTableName()); |
241 | 0 | const auto oIterMappingTableName = |
242 | 0 | m_mapOldLayerNameToNew.find(poSrcRelationShip->GetMappingTableName()); |
243 | 0 | if (oIterLeftTableName == m_mapOldLayerNameToNew.end() && |
244 | 0 | oIterRightTableName == m_mapOldLayerNameToNew.end() && |
245 | 0 | oIterMappingTableName == m_mapOldLayerNameToNew.end()) |
246 | 0 | { |
247 | 0 | return poSrcRelationShip; |
248 | 0 | } |
249 | | |
250 | 0 | auto poNewRelationship = |
251 | 0 | std::make_unique<GDALRelationship>(*poSrcRelationShip); |
252 | 0 | if (oIterLeftTableName != m_mapOldLayerNameToNew.end()) |
253 | 0 | poNewRelationship->SetLeftTableName(oIterLeftTableName->second); |
254 | 0 | if (oIterRightTableName != m_mapOldLayerNameToNew.end()) |
255 | 0 | poNewRelationship->SetRightTableName(oIterRightTableName->second); |
256 | 0 | if (oIterMappingTableName != m_mapOldLayerNameToNew.end()) |
257 | 0 | poNewRelationship->SetMappingTableName(oIterMappingTableName->second); |
258 | |
|
259 | 0 | return m_relationships.insert({name, std::move(poNewRelationship)}) |
260 | 0 | .first->second.get(); |
261 | 0 | } |
262 | | |
263 | | } // namespace |
264 | | |
265 | | /************************************************************************/ |
266 | | /* TruncateUTF8ToMaxChar() */ |
267 | | /************************************************************************/ |
268 | | |
269 | | static void TruncateUTF8ToMaxChar(std::string &osStr, size_t maxCharCount) |
270 | 0 | { |
271 | 0 | size_t nCharacterCount = 0; |
272 | 0 | for (size_t i = 0; i < osStr.size(); ++i) |
273 | 0 | { |
274 | | // Is it first byte of a UTF-8 character? |
275 | 0 | if ((osStr[i] & 0xc0) != 0x80) |
276 | 0 | { |
277 | 0 | ++nCharacterCount; |
278 | 0 | if (nCharacterCount == maxCharCount) |
279 | 0 | { |
280 | 0 | osStr.resize(i + 1); |
281 | 0 | break; |
282 | 0 | } |
283 | 0 | } |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | /************************************************************************/ |
288 | | /* GDALVectorRenameLayerAlgorithm::RunStep() */ |
289 | | /************************************************************************/ |
290 | | |
291 | | bool GDALVectorRenameLayerAlgorithm::RunStep(GDALPipelineStepRunContext &) |
292 | 0 | { |
293 | 0 | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
294 | 0 | CPLAssert(poSrcDS); |
295 | | |
296 | 0 | CPLAssert(m_outputDataset.GetName().empty()); |
297 | 0 | CPLAssert(!m_outputDataset.GetDatasetRef()); |
298 | | |
299 | | // First pass over layer names to create new layer names matching specified |
300 | | // constraints |
301 | 0 | std::vector<std::string> aosNames; |
302 | 0 | std::map<std::string, int> oMapCountNames; |
303 | 0 | bool bNonUniqueNames = false; |
304 | 0 | const int nLayerCount = poSrcDS->GetLayerCount(); |
305 | 0 | for (const OGRLayer *poSrcLayer : poSrcDS->GetLayers()) |
306 | 0 | { |
307 | 0 | if ((m_inputLayerName == poSrcLayer->GetDescription() || |
308 | 0 | nLayerCount == 1) && |
309 | 0 | !m_outputLayerName.empty()) |
310 | 0 | { |
311 | 0 | aosNames.push_back(m_outputLayerName); |
312 | 0 | } |
313 | 0 | else |
314 | 0 | { |
315 | 0 | std::string osName(poSrcLayer->GetDescription()); |
316 | 0 | if (!m_reservedChars.empty()) |
317 | 0 | { |
318 | 0 | std::string osNewName; |
319 | 0 | for (char c : osName) |
320 | 0 | { |
321 | 0 | if (m_reservedChars.find(c) != std::string::npos) |
322 | 0 | { |
323 | 0 | if (!m_replacementChar.empty()) |
324 | 0 | osNewName += m_replacementChar; |
325 | 0 | } |
326 | 0 | else |
327 | 0 | { |
328 | 0 | osNewName += c; |
329 | 0 | } |
330 | 0 | } |
331 | 0 | osName = std::move(osNewName); |
332 | 0 | } |
333 | 0 | if (m_filenameCompatible) |
334 | 0 | { |
335 | 0 | osName = CPLLaunderForFilenameSafe( |
336 | 0 | osName, m_replacementChar.c_str()[0]); |
337 | 0 | } |
338 | 0 | if (m_ascii) |
339 | 0 | { |
340 | 0 | char *pszStr = CPLUTF8ForceToASCII( |
341 | 0 | osName.c_str(), m_replacementChar.c_str()[0]); |
342 | 0 | osName = pszStr; |
343 | 0 | CPLFree(pszStr); |
344 | 0 | } |
345 | 0 | if (m_lowerCase) |
346 | 0 | { |
347 | 0 | for (char &c : osName) |
348 | 0 | { |
349 | 0 | if (c >= 'A' && c <= 'Z') |
350 | 0 | c = c - 'A' + 'a'; |
351 | 0 | } |
352 | 0 | } |
353 | 0 | if (m_maxLength > 0) |
354 | 0 | { |
355 | 0 | TruncateUTF8ToMaxChar(osName, m_maxLength); |
356 | 0 | } |
357 | 0 | if (++oMapCountNames[osName] > 1) |
358 | 0 | bNonUniqueNames = true; |
359 | 0 | aosNames.push_back(std::move(osName)); |
360 | 0 | } |
361 | 0 | } |
362 | | |
363 | | // Extra optional pass if some names are not unique |
364 | 0 | if (bNonUniqueNames) |
365 | 0 | { |
366 | 0 | std::map<std::string, int> oMapCurCounter; |
367 | 0 | bool bUniquenessPossible = true; |
368 | 0 | for (auto &osName : aosNames) |
369 | 0 | { |
370 | 0 | const int nCountForName = oMapCountNames[osName]; |
371 | 0 | if (nCountForName > 1) |
372 | 0 | { |
373 | 0 | const int nCounter = ++oMapCurCounter[osName]; |
374 | 0 | std::string osSuffix("_"); |
375 | 0 | if (nCountForName <= 9) |
376 | 0 | osSuffix += CPLSPrintf("%d", nCounter); |
377 | 0 | else if (nCountForName <= 99) |
378 | 0 | osSuffix += CPLSPrintf("%02d", nCounter); |
379 | 0 | else |
380 | 0 | osSuffix += CPLSPrintf("%03d", nCounter); |
381 | 0 | const size_t nNameLen = CPLStrlenUTF8Ex(osName.c_str()); |
382 | 0 | if (m_maxLength > 0 && nNameLen + osSuffix.size() > |
383 | 0 | static_cast<size_t>(m_maxLength)) |
384 | 0 | { |
385 | 0 | if (nNameLen > osSuffix.size()) |
386 | 0 | { |
387 | 0 | TruncateUTF8ToMaxChar(osName, |
388 | 0 | nNameLen - osSuffix.size()); |
389 | 0 | osName += osSuffix; |
390 | 0 | } |
391 | 0 | else if (bUniquenessPossible) |
392 | 0 | { |
393 | 0 | ReportError(CE_Warning, CPLE_AppDefined, |
394 | 0 | "Cannot create unique name for '%s' while " |
395 | 0 | "respecting %d maximum length", |
396 | 0 | osName.c_str(), m_maxLength); |
397 | 0 | bUniquenessPossible = false; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | else |
401 | 0 | { |
402 | 0 | osName += osSuffix; |
403 | 0 | } |
404 | 0 | } |
405 | 0 | } |
406 | 0 | } |
407 | |
|
408 | 0 | auto outDS = std::make_unique<GDALVectorRenameLayerAlgorithmDataset>( |
409 | 0 | *poSrcDS, aosNames); |
410 | | |
411 | | // Final pass to create output layers |
412 | 0 | size_t i = 0; |
413 | 0 | for (OGRLayer *poSrcLayer : poSrcDS->GetLayers()) |
414 | 0 | { |
415 | 0 | if (poSrcLayer->GetDescription() != aosNames[i]) |
416 | 0 | { |
417 | 0 | auto poLayer = |
418 | 0 | std::make_unique<GDALVectorRenameLayerAlgorithmLayer>( |
419 | 0 | *poSrcLayer, aosNames[i]); |
420 | 0 | outDS->AddLayer(*poSrcLayer, std::move(poLayer)); |
421 | 0 | } |
422 | 0 | else |
423 | 0 | { |
424 | 0 | outDS->AddLayer( |
425 | 0 | *poSrcLayer, |
426 | 0 | std::make_unique<GDALVectorPipelinePassthroughLayer>( |
427 | 0 | *poSrcLayer)); |
428 | 0 | } |
429 | 0 | ++i; |
430 | 0 | } |
431 | |
|
432 | 0 | m_outputDataset.Set(std::move(outDS)); |
433 | |
|
434 | 0 | return true; |
435 | 0 | } |
436 | | |
437 | | GDALVectorRenameLayerAlgorithmStandalone:: |
438 | 0 | ~GDALVectorRenameLayerAlgorithmStandalone() = default; |
439 | | |
440 | | //! @endcond |