5

I want to create empty GeoPackage layers with a defined schema. This seems to just work with Fiona regardless of whether I have spatial or non-spatial layers:

def create_blank_gpkg_layer(
 gpkg: Path, layer_name: str, crs: dict, schema: dict
) -> dict:
 """
 Thin wrapper around fiona.open to create an empty layer in a geopackage
 If the geopackage doesn't exist it is created.
 Any existing geopackage or layer that shares the same names are overwritten
 """
 gpkg.parent.mkdir(exist_ok=True, parents=True)
 with fiona.open(
 gpkg,
 "w",
 driver="GPKG",
 crs=crs,
 layer=layer_name,
 schema=schema,
 overwrite=True,
 ) as new_layer:
 meta = new_layer.meta
 print(f"{layer_name} created in {gpkg}")
 return meta
wgs_crs = from_epsg(4326) 
poly_schema = {
 "properties": OrderedDict([("name":"str"),("code":"int")]),
 "geometry": "Polygon"
}
not_spatial_schema = {"properties": OrderedDict([("name":"str"),("code":"int")])}
create_blank_gpkg_layer(gpkg, layer_name=poly, crs=crs, schema=poly_schema)
create_blank_gpkg_layer(gpkg, layer_name=not_spatial, crs=crs, schema=not_spatial_schema)

This is fine, but I'd rather not have fiona as a dependency if the capability is already within QGIS - which is where the GeoPackages will end up, and I will have seperate code to configure symbology etc.

So I'd like to do the same using the PyQGIS QgsVectorFileWriter or whatever is most appropriate.

I've managed to make a workaround that creates a memory layer and then writes it, but I feel like there is probably a more 'idiomatic' way of solving this, as I then have to get the layer I've created in the GeoPackage (the one I actually want) and add it to the map.

# Layers that have a geometry
foo_layer = QgsVectorLayer("Point?crs=epsg:4326&field=name:string", "foo layer", "memory")
params={'INPUT': foo_layer, 'OPTIONS':'-update -nln foo','OUTPUT': '/tmp/temp_gpkg.gpkg'}
processing.run("gdal:convertformat", params)
# Layers with no geometry
bar_layer = QgsVectorLayer('None', 'bar', 'memory')
bar_layer.startEditing()
bar_layer.addAttribute(QgsField('name',QVariant.String))
bar_layer.commitChanges()
params={'INPUT': bar_layer,'OPTIONS':'-update -nln bar','OUTPUT': '/tmp/temp_gpkg.gpkg'}
processing.run("gdal:convertformat", params)
Taras
35.7k5 gold badges77 silver badges151 bronze badges
asked Dec 2, 2021 at 18:53
2
  • It takes less writing to do it with ogrinfo ogrinfo -sql "create table foo (attr1 text, attr2 int)" test.gpkg. But if your aim is to create a table with a geometry column then it gets more complicated. Commented Dec 2, 2021 at 19:03
  • Indeed, I could do it all in sql registering the geometry columns etc, but thought there might be another way. Commented Dec 2, 2021 at 19:36

1 Answer 1

7

You can create a method like this:

def create_blank_gpkg_layer(gpkg_path: str, layer_name: str, geometry: int,
 crs: str, schema: QgsFields, append: bool = False
 ) -> bool:
 # To add a layer to an existing GPKG file, pass 'append' as True
 options = QgsVectorFileWriter.SaveVectorOptions()
 options.driverName = "GPKG"
 options.layerName = layer_name
 if append:
 options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
 writer = QgsVectorFileWriter.create(
 gpkg_path,
 schema,
 geometry,
 QgsCoordinateReferenceSystem(crs),
 QgsCoordinateTransformContext(),
 options)
 del writer
 return True

And then use it in this way for spatial layers:

# Create a layer
gpkg_path = "/tmp/test.gpkg"
layer_name = "my_layer"
geom = QgsWkbTypes.PolygonZ
crs = 'epsg:4326'
schema = QgsFields()
schema.append(QgsField("double_field", QVariant.Double))
schema.append(QgsField("text_field", QVariant.String))
create_blank_gpkg_layer(gpkg_path, layer_name, geom, crs, schema)

Or in this way, for non-spatial layers:

# Create a table
layer_name = "my_table"
geom = QgsWkbTypes.NoGeometry
crs = ''
schema = QgsFields()
schema.append(QgsField("int_field", QVariant.Int))
schema.append(QgsField("bool_field", QVariant.Bool))
create_blank_gpkg_layer(gpkg_path, layer_name, geom, crs, schema, True)

Adapted from: https://github.com/qgis/QGIS/issues/37386

answered Dec 3, 2021 at 1:56
1
  • Excellent, that's much better. QgsWkbTypes.NoGeometry was the missing piece! Commented Dec 3, 2021 at 9:18

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.