Skip to content

Commit 683b85e

Browse files
committed
gdal vector create: add a --fid option, and other tweaks/fixes discovered along the way
1 parent e9e5d97 commit 683b85e

File tree

6 files changed

+127
-60
lines changed

6 files changed

+127
-60
lines changed

apps/gdalalg_vector_create.cpp

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,7 @@ GDALVectorCreateAlgorithm::GDALVectorCreateAlgorithm(bool standaloneStep)
4747
// Note: this is required despite SetAddDefaultArguments(false)
4848
.SetAddUpsertArgument(false)
4949
.SetAddSkipErrorsArgument(false)
50-
.SetAddAppendLayerArgument(false)
51-
52-
),
53-
m_crs(), m_geometryType(), m_geometryFieldName("geom"),
54-
m_schemaJsonOrPath(), m_fieldDefinitions(), m_fieldStrDefinitions()
50+
.SetAddAppendLayerArgument(false))
5551
{
5652

5753
AddVectorInputArgs(false);
@@ -63,16 +59,17 @@ GDALVectorCreateAlgorithm::GDALVectorCreateAlgorithm(bool standaloneStep)
6359
auto &geomFieldNameArg =
6460
AddArg("geometry-field", 0,
6561
_("Name of the geometry field to create (if supported by the "
66-
"output "
67-
"format)"),
62+
"output format)"),
6863
&m_geometryFieldName)
6964
.SetMetaVar("GEOMETRY-FIELD")
70-
.SetDefault("geom");
65+
.SetDefault(m_geometryFieldName);
7166

7267
AddArg("crs", 0, _("Set CRS"), &m_crs)
7368
.AddHiddenAlias("srs")
7469
.SetIsCRSArg(/*noneAllowed=*/false);
7570

71+
AddArg("fid", 0, _("FID column name"), &m_fidColumnName);
72+
7673
constexpr auto inputMutexGroup = "like-schema-field";
7774

7875
// Apply mutex to GDAL_ARG_NAME_INPUT
@@ -104,19 +101,12 @@ GDALVectorCreateAlgorithm::GDALVectorCreateAlgorithm(bool standaloneStep)
104101
((!m_geometryFieldName.empty() &&
105102
geomFieldNameArg.IsExplicitlySet()) ||
106103
!m_geometryType.empty() || !m_fieldDefinitions.empty() ||
107-
!m_crs.empty()))
104+
!m_crs.empty() || !m_fidColumnName.empty()))
108105
{
109106
ReportError(CE_Failure, CPLE_AppDefined,
110107
"When --schema or --like is specified, "
111-
"--geometry-field, --geometry-type, "
112-
"--field and --crs options must not be specified.");
113-
return false;
114-
}
115-
if (!m_geometryType.empty() && m_crs.empty())
116-
{
117-
ReportError(CE_Failure, CPLE_AppDefined,
118-
"When --geometry-type is specified, --crs must "
119-
"also be specified");
108+
"--geometry-field, --geometry-type, --field, "
109+
"--crs and --fid options must not be specified.");
120110
return false;
121111
}
122112
return true;
@@ -131,8 +121,9 @@ bool GDALVectorCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
131121
{
132122

133123
const std::string &datasetName = m_outputDataset.GetName();
134-
auto outputLayerName =
135-
m_outputLayerName.empty() ? datasetName : m_outputLayerName;
124+
const std::string outputLayerName =
125+
m_outputLayerName.empty() ? CPLGetBasenameSafe(datasetName.c_str())
126+
: m_outputLayerName;
136127

137128
std::unique_ptr<GDALDataset> poDstDS;
138129
poDstDS.reset(GDALDataset::Open(datasetName.c_str(),
@@ -265,13 +256,6 @@ bool GDALVectorCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
265256
return false;
266257
}
267258

268-
if (EQUAL(poDstDriver->GetDescription(), "ESRI Shapefile") &&
269-
EQUAL(CPLGetExtensionSafe(poDstDS->GetDescription()).c_str(), "shp") &&
270-
poDstDS->GetLayerCount() <= 1)
271-
{
272-
outputLayerName = CPLGetBasenameSafe(outputLayerName.c_str());
273-
}
274-
275259
// An OGR_SCHEMA has been provided
276260
if (!oSchemaOverride.GetLayerOverrides().empty())
277261
{
@@ -353,6 +337,7 @@ bool GDALVectorCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
353337
: userSpecifiedNewName;
354338

355339
if (!CreateLayer(poDstDS.get(), outputLayerNewName,
340+
oLayerOverride.GetFIDColumnName(),
356341
oLayerOverride.GetFieldDefinitions(),
357342
oLayerOverride.GetGeomFieldDefinitions()))
358343
{
@@ -370,7 +355,8 @@ bool GDALVectorCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
370355
{
371356
const OGRwkbGeometryType eDstType =
372357
OGRFromOGCGeomType(m_geometryType.c_str());
373-
if (eDstType == wkbUnknown)
358+
if (eDstType == wkbUnknown &&
359+
!STARTS_WITH_CI(m_geometryType.c_str(), "GEOMETRY"))
374360
{
375361
ReportError(CE_Failure, CPLE_AppDefined,
376362
"Unsupported geometry type: '%s'.",
@@ -403,16 +389,8 @@ bool GDALVectorCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
403389
}
404390
}
405391

406-
if (EQUAL(poDstDriver->GetDescription(), "ESRI Shapefile") &&
407-
EQUAL(CPLGetExtensionSafe(poDstDS->GetDescription()).c_str(),
408-
"shp") &&
409-
poDstDS->GetLayerCount() <= 1)
410-
{
411-
outputLayerName = CPLGetBasenameSafe(poDstDS->GetDescription());
412-
}
413-
414-
if (!CreateLayer(poDstDS.get(), outputLayerName, GetOutputFields(),
415-
geometryFieldDefinitions))
392+
if (!CreateLayer(poDstDS.get(), outputLayerName, m_fidColumnName,
393+
GetOutputFields(), geometryFieldDefinitions))
416394
{
417395
ReportError(CE_Failure, CPLE_AppDefined,
418396
"Cannot create layer '%s'.", outputLayerName.c_str());
@@ -452,6 +430,7 @@ std::vector<OGRFieldDefn> GDALVectorCreateAlgorithm::GetOutputFields() const
452430
/************************************************************************/
453431
bool GDALVectorCreateAlgorithm::CreateLayer(
454432
GDALDataset *poDstDS, const std::string &layerName,
433+
const std::string &fidColumnName,
455434
const std::vector<OGRFieldDefn> &fieldDefinitions,
456435
const std::vector<OGRGeomFieldDefn> &geometryFieldDefinitions) const
457436
{
@@ -517,10 +496,19 @@ bool GDALVectorCreateAlgorithm::CreateLayer(
517496

518497
if (!poDstLayer)
519498
{
520-
521-
poDstLayer = poDstDS->CreateLayer(
522-
layerName.c_str(), poGeomFieldDefn.get(),
523-
CPLStringList(GetLayerCreationOptions()).List());
499+
CPLStringList aosCreationOptions(GetLayerCreationOptions());
500+
if (aosCreationOptions.FetchNameValue("FID") == nullptr &&
501+
!fidColumnName.empty())
502+
{
503+
auto poDstDriver = poDstDS->GetDriver();
504+
if (poDstDriver && poDstDriver->HasLayerCreationOption("FID"))
505+
{
506+
aosCreationOptions.SetNameValue("FID", fidColumnName.c_str());
507+
}
508+
}
509+
poDstLayer =
510+
poDstDS->CreateLayer(layerName.c_str(), poGeomFieldDefn.get(),
511+
aosCreationOptions.List());
524512
}
525513

526514
if (!poDstLayer)

apps/gdalalg_vector_create.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,17 @@ class GDALVectorCreateAlgorithm /* non final */
6363
*/
6464
bool CreateLayer(
6565
GDALDataset *poDstDS, const std::string &layerName,
66+
const std::string &fidColumnName,
6667
const std::vector<OGRFieldDefn> &fieldDefinitions,
6768
const std::vector<OGRGeomFieldDefn> &geometryFieldDefinitions) const;
6869

69-
std::string m_crs;
70-
std::string m_geometryType;
71-
std::string m_geometryFieldName;
72-
std::string m_schemaJsonOrPath;
73-
std::vector<OGRFieldDefn> m_fieldDefinitions;
74-
std::vector<std::string> m_fieldStrDefinitions;
70+
std::string m_crs{};
71+
std::string m_fidColumnName{};
72+
std::string m_geometryType{};
73+
std::string m_geometryFieldName{"geom"};
74+
std::string m_schemaJsonOrPath{};
75+
std::vector<OGRFieldDefn> m_fieldDefinitions{};
76+
std::vector<std::string> m_fieldStrDefinitions{};
7577
};
7678

7779
/************************************************************************/

autotest/utilities/test_gdalalg_vector_create.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,15 +485,19 @@ def test_gdalalg_vector_create_layer_names(
485485
),
486486
(
487487
{"schema": r"{}", "geometry-type": "Point"},
488-
" When --schema or --like is specified, --geometry-field, --geometry-type, --field and --crs options must not be specified.",
488+
"When --schema or --like is specified, .*options must not be specified.",
489489
),
490490
(
491491
{"schema": r"{}", "geometry-field": "geomm"},
492-
" When --schema or --like is specified, --geometry-field, --geometry-type, --field and --crs options must not be specified.",
492+
"When --schema or --like is specified, .*options must not be specified.",
493493
),
494494
(
495495
{"schema": r"{}", "crs": "EPSG:4326"},
496-
" When --schema or --like is specified, --geometry-field, --geometry-type, --field and --crs options must not be specified.",
496+
"When --schema or --like is specified, .*options must not be specified.",
497+
),
498+
(
499+
{"schema": r"{}", "fid": "fid"},
500+
"When --schema or --like is specified, .*options must not be specified.",
497501
),
498502
],
499503
)
@@ -612,3 +616,48 @@ def test_gdalalg_vector_create_datetime_timezone_round_trip(tmp_vsimem):
612616
assert (
613617
json_field_defn["timezone"] == "+11:30"
614618
), "Unexpected timezone information in exported schema"
619+
620+
621+
@pytest.mark.require_driver("GPKG")
622+
def test_gdalalg_vector_create_geometry_field_without_crs(tmp_vsimem):
623+
624+
with gdal.alg.vector.create(
625+
output=tmp_vsimem / "out.gpkg", geometry_type="GEOMETRY"
626+
) as alg:
627+
out_ds = alg.Output()
628+
out_lyr = out_ds.GetLayer(0)
629+
assert out_lyr.GetName() == "out"
630+
assert out_lyr.GetFIDColumn() == "fid"
631+
assert out_lyr.GetGeometryColumn() == "geom"
632+
assert out_lyr.GetGeomType() == ogr.wkbUnknown
633+
assert out_lyr.GetSpatialRef() is None
634+
assert out_lyr.GetLayerDefn().GetFieldCount() == 0
635+
636+
637+
@pytest.mark.require_driver("GPKG")
638+
def test_gdalalg_vector_create_implicit_fid_from_source(tmp_vsimem):
639+
640+
src_ds = gdal.GetDriverByName("GPKG").CreateVector(tmp_vsimem / "src.gpkg")
641+
src_ds.CreateLayer("test", options=["FID=my_fid"])
642+
643+
with gdal.alg.vector.create(input=src_ds, output=tmp_vsimem / "out.gpkg") as alg:
644+
out_ds = alg.Output()
645+
out_lyr = out_ds.GetLayer(0)
646+
assert out_lyr.GetName() == "test"
647+
assert out_lyr.GetFIDColumn() == "my_fid"
648+
assert out_lyr.GetGeometryColumn() == "geom"
649+
assert out_lyr.GetGeomType() == ogr.wkbUnknown
650+
assert out_lyr.GetSpatialRef() is None
651+
assert out_lyr.GetLayerDefn().GetFieldCount() == 0
652+
653+
654+
@pytest.mark.require_driver("GPKG")
655+
def test_gdalalg_vector_create_explicit_fid(tmp_vsimem):
656+
657+
with gdal.alg.vector.create(output=tmp_vsimem / "out.gpkg", fid="my_fid") as alg:
658+
out_ds = alg.Output()
659+
out_lyr = out_ds.GetLayer(0)
660+
assert out_lyr.GetName() == "out"
661+
assert out_lyr.GetFIDColumn() == "my_fid"
662+
assert out_lyr.GetLayerDefn().GetFieldCount() == 0
663+
assert out_lyr.GetLayerDefn().GetGeomFieldCount() == 0

doc/source/programs/gdal_vector_create.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ Program-Specific Options
118118

119119
Mutually exclusive with :option:`--schema` and with :option:`--like`.
120120

121+
.. option:: --fid <FID>
122+
123+
Defines the name of the Feature Identifier (FID) column, for drivers that
124+
support setting it (those which declare a ``FID`` layer creation option)
125+
126+
Mutually exclusive with :option:`--schema` and with :option:`--like`.
127+
121128

122129
Standard Options
123130
----------------
@@ -151,4 +158,4 @@ Examples
151158
:title: Create a new vector dataset with a layer named `countries_new` based on the layer `countries` of an existing dataset
152159

153160
.. command-output:: gdal vector create --like ../data/poly.gpkg --input-layer poly --output-layer areas_new ./areas.gpkg
154-
:cwd: ../../data
161+
:cwd: ../../data

ogr/ogr_schema_override.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ bool OGRSchemaOverride::LoadFromJSON(const std::string &osJSON,
6666
oLayerOverride.SetLayerName(osLayerName);
6767
oLayerOverride.SetFullOverride(bSchemaFullOverride);
6868

69+
oLayerOverride.SetFIDColumnName(
70+
oLayer.GetString("fidColumnName"));
71+
6972
if (oLayerFields.Size() > 0 && !osLayerName.empty())
7073
{
7174
for (const auto &oField : oLayerFields)
@@ -348,19 +351,21 @@ bool OGRSchemaOverride::LoadFromJSON(const std::string &osJSON,
348351
const auto osGeomFieldName =
349352
oGeometryField.GetString("name");
350353
oGeomFieldOverride.SetFieldName(osGeomFieldName);
351-
const CPLString oGeometryType(
354+
const CPLString osGeometryType(
352355
CPLString(oGeometryField.GetString("type"))
353356
.tolower());
354-
if (!oGeometryType.empty())
357+
if (!osGeometryType.empty())
355358
{
356359
const OGRwkbGeometryType eType =
357-
OGRFromOGCGeomType(oGeometryType.c_str());
358-
if (eType == wkbUnknown)
360+
OGRFromOGCGeomType(osGeometryType.c_str());
361+
if (eType == wkbUnknown &&
362+
!cpl::starts_with(osGeometryType,
363+
"geometry"))
359364
{
360365
CPLError(CE_Failure, CPLE_AppDefined,
361366
"Unsupported geometry field type: "
362367
"%s for geometry field %s",
363-
oGeometryType.c_str(),
368+
osGeometryType.c_str(),
364369
osGeomFieldName.c_str());
365370
return false;
366371
}
@@ -632,6 +637,12 @@ void OGRLayerSchemaOverride::SetLayerName(const std::string &osLayerName)
632637
m_osLayerName = osLayerName;
633638
}
634639

640+
void OGRLayerSchemaOverride::SetFIDColumnName(
641+
const std::string &osFIDColumnName)
642+
{
643+
m_osFIDColumnName = osFIDColumnName;
644+
}
645+
635646
void OGRLayerSchemaOverride::AddNamedFieldOverride(
636647
const std::string &osFieldName, const OGRFieldDefnOverride &oFieldOverride)
637648
{
@@ -649,6 +660,11 @@ const std::string &OGRLayerSchemaOverride::GetLayerName() const
649660
return m_osLayerName;
650661
}
651662

663+
const std::string &OGRLayerSchemaOverride::GetFIDColumnName() const
664+
{
665+
return m_osFIDColumnName;
666+
}
667+
652668
const std::map<std::string, OGRFieldDefnOverride> &
653669
OGRLayerSchemaOverride::GetNamedFieldOverrides() const
654670
{
@@ -706,9 +722,9 @@ void OGRLayerSchemaOverride::SetFullOverride(bool bIsFullOverride)
706722

707723
bool OGRLayerSchemaOverride::IsValid() const
708724
{
709-
bool isValid =
710-
!m_osLayerName.empty() &&
711-
(!m_oNamedFieldOverrides.empty() || !m_aoUnnamedFieldOverrides.empty());
725+
bool isValid = !m_osLayerName.empty() &&
726+
(!m_oNamedFieldOverrides.empty() ||
727+
!m_aoUnnamedFieldOverrides.empty() || m_bIsFullOverride);
712728
for (const auto &oFieldOverrideIter : m_oNamedFieldOverrides)
713729
{
714730
isValid &= !oFieldOverrideIter.first.empty();

ogr/ogr_schema_override.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,17 @@ class CPL_DLL OGRLayerSchemaOverride
261261

262262
void SetLayerName(const std::string &osLayerName);
263263

264+
void SetFIDColumnName(const std::string &osFIDColumnName);
265+
264266
void AddNamedFieldOverride(const std::string &osFieldName,
265267
const OGRFieldDefnOverride &oFieldOverride);
266268

267269
void AddUnnamedFieldOverride(const OGRFieldDefnOverride &oFieldOverride);
268270

269271
const std::string &GetLayerName() const;
270272

273+
const std::string &GetFIDColumnName() const;
274+
271275
const std::map<std::string, OGRFieldDefnOverride> &
272276
GetNamedFieldOverrides() const;
273277

@@ -293,6 +297,7 @@ class CPL_DLL OGRLayerSchemaOverride
293297

294298
private:
295299
std::string m_osLayerName{};
300+
std::string m_osFIDColumnName{};
296301
std::map<std::string, OGRFieldDefnOverride> m_oNamedFieldOverrides{};
297302
std::vector<OGRFieldDefnOverride> m_aoUnnamedFieldOverrides{};
298303
std::vector<OGRGeomFieldDefnOverride> m_aoGeomFieldOverrides{};

0 commit comments

Comments
 (0)