/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 | | OGRFeatureDefn *const m_poFeatureDefn = nullptr; |
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); |
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 | m_poFeatureDefn->Reference(); |
136 | 0 | } |
137 | | |
138 | | ~GDALVectorRenameLayerAlgorithmLayer() override |
139 | 0 | { |
140 | 0 | m_poFeatureDefn->Release(); |
141 | 0 | } |
142 | | |
143 | | const OGRFeatureDefn *GetLayerDefn() const override |
144 | 0 | { |
145 | 0 | return m_poFeatureDefn; |
146 | 0 | } |
147 | | |
148 | | GIntBig GetFeatureCount(int bForce) override |
149 | 0 | { |
150 | 0 | return m_srcLayer.GetFeatureCount(bForce); |
151 | 0 | } |
152 | | |
153 | | OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent, |
154 | | bool bForce) override |
155 | 0 | { |
156 | 0 | return m_srcLayer.GetExtent(iGeomField, psExtent, bForce); |
157 | 0 | } |
158 | | |
159 | | OGRErr SetIgnoredFields(CSLConstList papszFields) override |
160 | 0 | { |
161 | 0 | return m_srcLayer.SetIgnoredFields(papszFields); |
162 | 0 | } |
163 | | |
164 | | OGRErr SetAttributeFilter(const char *pszAttributeFilter) override |
165 | 0 | { |
166 | 0 | OGRLayer::SetAttributeFilter(pszAttributeFilter); |
167 | 0 | return m_srcLayer.SetAttributeFilter(pszAttributeFilter); |
168 | 0 | } |
169 | | |
170 | | OGRGeometry *GetSpatialFilter() override |
171 | 0 | { |
172 | 0 | return m_srcLayer.GetSpatialFilter(); |
173 | 0 | } |
174 | | |
175 | | OGRErr ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom) override |
176 | 0 | { |
177 | 0 | return m_srcLayer.SetSpatialFilter(iGeomField, poGeom); |
178 | 0 | } |
179 | | |
180 | | OGRFeature *GetFeature(GIntBig nFID) override |
181 | 0 | { |
182 | 0 | auto poSrcFeature = |
183 | 0 | std::unique_ptr<OGRFeature>(m_srcLayer.GetFeature(nFID)); |
184 | 0 | if (!poSrcFeature) |
185 | 0 | return nullptr; |
186 | 0 | poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn); |
187 | 0 | return poSrcFeature.release(); |
188 | 0 | } |
189 | | |
190 | | int TestCapability(const char *pszCap) const override |
191 | 0 | { |
192 | 0 | return m_srcLayer.TestCapability(pszCap); |
193 | 0 | } |
194 | | }; |
195 | | |
196 | | /************************************************************************/ |
197 | | /* GDALVectorRenameLayerAlgorithmDataset */ |
198 | | /************************************************************************/ |
199 | | |
200 | | class GDALVectorRenameLayerAlgorithmDataset final |
201 | | : public GDALVectorPipelineOutputDataset |
202 | | { |
203 | | public: |
204 | | GDALVectorRenameLayerAlgorithmDataset( |
205 | | GDALDataset &oSrcDS, const std::vector<std::string> &aosNewLayerNames) |
206 | 0 | : GDALVectorPipelineOutputDataset(oSrcDS) |
207 | 0 | { |
208 | 0 | const int nLayerCount = oSrcDS.GetLayerCount(); |
209 | 0 | CPLAssert(aosNewLayerNames.size() == static_cast<size_t>(nLayerCount)); |
210 | 0 | for (int i = 0; i < nLayerCount; ++i) |
211 | 0 | { |
212 | 0 | m_mapOldLayerNameToNew[oSrcDS.GetLayer(i)->GetName()] = |
213 | 0 | aosNewLayerNames[i]; |
214 | 0 | } |
215 | 0 | } |
216 | | |
217 | | const GDALRelationship * |
218 | | GetRelationship(const std::string &name) const override; |
219 | | |
220 | | private: |
221 | | std::map<std::string, std::string> m_mapOldLayerNameToNew{}; |
222 | | mutable std::map<std::string, std::unique_ptr<GDALRelationship>> |
223 | | m_relationships{}; |
224 | | }; |
225 | | |
226 | | /************************************************************************/ |
227 | | /* GetRelationship() */ |
228 | | /************************************************************************/ |
229 | | |
230 | | const GDALRelationship *GDALVectorRenameLayerAlgorithmDataset::GetRelationship( |
231 | | const std::string &name) const |
232 | 0 | { |
233 | 0 | const auto oIterRelationships = m_relationships.find(name); |
234 | 0 | if (oIterRelationships != m_relationships.end()) |
235 | 0 | return oIterRelationships->second.get(); |
236 | | |
237 | 0 | const GDALRelationship *poSrcRelationShip = m_srcDS.GetRelationship(name); |
238 | 0 | if (!poSrcRelationShip) |
239 | 0 | return nullptr; |
240 | 0 | const auto oIterLeftTableName = |
241 | 0 | m_mapOldLayerNameToNew.find(poSrcRelationShip->GetLeftTableName()); |
242 | 0 | const auto oIterRightTableName = |
243 | 0 | m_mapOldLayerNameToNew.find(poSrcRelationShip->GetRightTableName()); |
244 | 0 | const auto oIterMappingTableName = |
245 | 0 | m_mapOldLayerNameToNew.find(poSrcRelationShip->GetMappingTableName()); |
246 | 0 | if (oIterLeftTableName == m_mapOldLayerNameToNew.end() && |
247 | 0 | oIterRightTableName == m_mapOldLayerNameToNew.end() && |
248 | 0 | oIterMappingTableName == m_mapOldLayerNameToNew.end()) |
249 | 0 | { |
250 | 0 | return poSrcRelationShip; |
251 | 0 | } |
252 | | |
253 | 0 | auto poNewRelationship = |
254 | 0 | std::make_unique<GDALRelationship>(*poSrcRelationShip); |
255 | 0 | if (oIterLeftTableName != m_mapOldLayerNameToNew.end()) |
256 | 0 | poNewRelationship->SetLeftTableName(oIterLeftTableName->second); |
257 | 0 | if (oIterRightTableName != m_mapOldLayerNameToNew.end()) |
258 | 0 | poNewRelationship->SetRightTableName(oIterRightTableName->second); |
259 | 0 | if (oIterMappingTableName != m_mapOldLayerNameToNew.end()) |
260 | 0 | poNewRelationship->SetMappingTableName(oIterMappingTableName->second); |
261 | |
|
262 | 0 | return m_relationships.insert({name, std::move(poNewRelationship)}) |
263 | 0 | .first->second.get(); |
264 | 0 | } |
265 | | |
266 | | } // namespace |
267 | | |
268 | | /************************************************************************/ |
269 | | /* TruncateUTF8ToMaxChar() */ |
270 | | /************************************************************************/ |
271 | | |
272 | | static void TruncateUTF8ToMaxChar(std::string &osStr, size_t maxCharCount) |
273 | 0 | { |
274 | 0 | size_t nCharacterCount = 0; |
275 | 0 | for (size_t i = 0; i < osStr.size(); ++i) |
276 | 0 | { |
277 | | // Is it first byte of a UTF-8 character? |
278 | 0 | if ((osStr[i] & 0xc0) != 0x80) |
279 | 0 | { |
280 | 0 | ++nCharacterCount; |
281 | 0 | if (nCharacterCount == maxCharCount) |
282 | 0 | { |
283 | 0 | osStr.resize(i + 1); |
284 | 0 | break; |
285 | 0 | } |
286 | 0 | } |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | | /************************************************************************/ |
291 | | /* GDALVectorRenameLayerAlgorithm::RunStep() */ |
292 | | /************************************************************************/ |
293 | | |
294 | | bool GDALVectorRenameLayerAlgorithm::RunStep(GDALPipelineStepRunContext &) |
295 | 0 | { |
296 | 0 | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
297 | 0 | CPLAssert(poSrcDS); |
298 | | |
299 | 0 | CPLAssert(m_outputDataset.GetName().empty()); |
300 | 0 | CPLAssert(!m_outputDataset.GetDatasetRef()); |
301 | | |
302 | | // First pass over layer names to create new layer names matching specified |
303 | | // constraints |
304 | 0 | std::vector<std::string> aosNames; |
305 | 0 | std::map<std::string, int> oMapCountNames; |
306 | 0 | bool bNonUniqueNames = false; |
307 | 0 | const int nLayerCount = poSrcDS->GetLayerCount(); |
308 | 0 | for (int i = 0; i < nLayerCount; ++i) |
309 | 0 | { |
310 | 0 | const OGRLayer *poSrcLayer = poSrcDS->GetLayer(i); |
311 | 0 | if ((m_inputLayerName == poSrcLayer->GetDescription() || |
312 | 0 | nLayerCount == 1) && |
313 | 0 | !m_outputLayerName.empty()) |
314 | 0 | { |
315 | 0 | aosNames.push_back(m_outputLayerName); |
316 | 0 | } |
317 | 0 | else |
318 | 0 | { |
319 | 0 | std::string osName(poSrcLayer->GetDescription()); |
320 | 0 | if (!m_reservedChars.empty()) |
321 | 0 | { |
322 | 0 | std::string osNewName; |
323 | 0 | for (char c : osName) |
324 | 0 | { |
325 | 0 | if (m_reservedChars.find(c) != std::string::npos) |
326 | 0 | { |
327 | 0 | if (!m_replacementChar.empty()) |
328 | 0 | osNewName += m_replacementChar; |
329 | 0 | } |
330 | 0 | else |
331 | 0 | { |
332 | 0 | osNewName += c; |
333 | 0 | } |
334 | 0 | } |
335 | 0 | osName = std::move(osNewName); |
336 | 0 | } |
337 | 0 | if (m_filenameCompatible) |
338 | 0 | { |
339 | 0 | osName = CPLLaunderForFilenameSafe( |
340 | 0 | osName, m_replacementChar.c_str()[0]); |
341 | 0 | } |
342 | 0 | if (m_ascii) |
343 | 0 | { |
344 | 0 | char *pszStr = CPLUTF8ForceToASCII( |
345 | 0 | osName.c_str(), m_replacementChar.c_str()[0]); |
346 | 0 | osName = pszStr; |
347 | 0 | CPLFree(pszStr); |
348 | 0 | } |
349 | 0 | if (m_lowerCase) |
350 | 0 | { |
351 | 0 | for (char &c : osName) |
352 | 0 | { |
353 | 0 | if (c >= 'A' && c <= 'Z') |
354 | 0 | c = c - 'A' + 'a'; |
355 | 0 | } |
356 | 0 | } |
357 | 0 | if (m_maxLength > 0) |
358 | 0 | { |
359 | 0 | TruncateUTF8ToMaxChar(osName, m_maxLength); |
360 | 0 | } |
361 | 0 | if (++oMapCountNames[osName] > 1) |
362 | 0 | bNonUniqueNames = true; |
363 | 0 | aosNames.push_back(std::move(osName)); |
364 | 0 | } |
365 | 0 | } |
366 | | |
367 | | // Extra optional pass if some names are not unique |
368 | 0 | if (bNonUniqueNames) |
369 | 0 | { |
370 | 0 | std::map<std::string, int> oMapCurCounter; |
371 | 0 | bool bUniquenessPossible = true; |
372 | 0 | for (auto &osName : aosNames) |
373 | 0 | { |
374 | 0 | const int nCountForName = oMapCountNames[osName]; |
375 | 0 | if (nCountForName > 1) |
376 | 0 | { |
377 | 0 | const int nCounter = ++oMapCurCounter[osName]; |
378 | 0 | std::string osSuffix("_"); |
379 | 0 | if (nCountForName <= 9) |
380 | 0 | osSuffix += CPLSPrintf("%d", nCounter); |
381 | 0 | else if (nCountForName <= 99) |
382 | 0 | osSuffix += CPLSPrintf("%02d", nCounter); |
383 | 0 | else |
384 | 0 | osSuffix += CPLSPrintf("%03d", nCounter); |
385 | 0 | const size_t nNameLen = CPLStrlenUTF8Ex(osName.c_str()); |
386 | 0 | if (m_maxLength > 0 && nNameLen + osSuffix.size() > |
387 | 0 | static_cast<size_t>(m_maxLength)) |
388 | 0 | { |
389 | 0 | if (nNameLen > osSuffix.size()) |
390 | 0 | { |
391 | 0 | TruncateUTF8ToMaxChar(osName, |
392 | 0 | nNameLen - osSuffix.size()); |
393 | 0 | osName += osSuffix; |
394 | 0 | } |
395 | 0 | else if (bUniquenessPossible) |
396 | 0 | { |
397 | 0 | ReportError(CE_Warning, CPLE_AppDefined, |
398 | 0 | "Cannot create unique name for '%s' while " |
399 | 0 | "respecting %d maximum length", |
400 | 0 | osName.c_str(), m_maxLength); |
401 | 0 | bUniquenessPossible = false; |
402 | 0 | } |
403 | 0 | } |
404 | 0 | else |
405 | 0 | { |
406 | 0 | osName += osSuffix; |
407 | 0 | } |
408 | 0 | } |
409 | 0 | } |
410 | 0 | } |
411 | |
|
412 | 0 | auto outDS = std::make_unique<GDALVectorRenameLayerAlgorithmDataset>( |
413 | 0 | *poSrcDS, aosNames); |
414 | | |
415 | | // Final pass to create output layers |
416 | 0 | for (int i = 0; i < nLayerCount; ++i) |
417 | 0 | { |
418 | 0 | OGRLayer *poSrcLayer = poSrcDS->GetLayer(i); |
419 | 0 | if (poSrcLayer->GetDescription() != aosNames[i]) |
420 | 0 | { |
421 | 0 | auto poLayer = |
422 | 0 | std::make_unique<GDALVectorRenameLayerAlgorithmLayer>( |
423 | 0 | *poSrcLayer, aosNames[i]); |
424 | 0 | outDS->AddLayer(*poSrcLayer, std::move(poLayer)); |
425 | 0 | } |
426 | 0 | else |
427 | 0 | { |
428 | 0 | outDS->AddLayer( |
429 | 0 | *poSrcLayer, |
430 | 0 | std::make_unique<GDALVectorPipelinePassthroughLayer>( |
431 | 0 | *poSrcLayer)); |
432 | 0 | } |
433 | 0 | } |
434 | |
|
435 | 0 | m_outputDataset.Set(std::move(outDS)); |
436 | |
|
437 | 0 | return true; |
438 | 0 | } |
439 | | |
440 | | GDALVectorRenameLayerAlgorithmStandalone:: |
441 | 0 | ~GDALVectorRenameLayerAlgorithmStandalone() = default; |
442 | | |
443 | | //! @endcond |