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