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)
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.
    – user30184
    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.
    – RoperMaps
    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

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

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.