4

I want to edit some fields in a GeoPackage (download a.gpkg)

# -*- coding: utf-8 -*-
from osgeo import ogr
from time import clock

print ("Start",clock())
source = ogr.Open("c:/.../a.gpkg",update=True)
layer = source.GetLayerByName( "263c6845-e2c3-4eee-ae26-f0e9539f3c2bP" )
laydef = layer.GetLayerDefn()

i=0
feature = layer.GetNextFeature()
while feature:
    i=i+1
    # make some stuff
    # .....
    layer.SetFeature(feature)
    feature = layer.GetNextFeature()
source.Destroy()
print ("Finish",i,clock())

('Start', 3.4136520136791866e-07) ('Finish', 1612, 243.56666043106233)

It works, but it takes a long time (4 minutes for 1.600 records).

What is going wrong?

5
  • how big are your features? does it have indexes on? what "stuff" are you doing?
    – Ian Turton
    Commented Mar 29, 2018 at 10:51
  • 4 minutes without stuff. The GeoPackage is a result from a DXF-convert. I have linked the GeoPackage above Commented Mar 29, 2018 at 10:56
  • Spatial index already existing Commented Mar 29, 2018 at 17:21
  • 1
    Does your code make a new transaction for each feature?
    – user30184
    Commented Mar 29, 2018 at 17:37
  • I do not know what happens internally. But the posted code is the complete code (which takes 4 minutes) Commented Mar 29, 2018 at 18:26

2 Answers 2

11
+50

Make a minor but important change and make all edits to happen within one transaction instead of doing one transaction for each row. Transactions are rather expensive for SQLite and therefore also for GeoPackage. Making less but bigger transactions is faster. You can find some numbers from https://stackoverflow.com/questions/1711631/improve-insert-per-second-performance-of-sqlite.

A modified script:

# -*- coding: utf-8 -*-
from osgeo import ogr
from time import clock

print ("Start",clock())
source = ogr.Open("a.gpkg",update=True)
layer = source.GetLayerByName( "263c6845-e2c3-4eee-ae26-f0e9539f3c2bP" )
laydef = layer.GetLayerDefn()
layer.StartTransaction()
i=0
feature = layer.GetNextFeature()
while feature:
    i=i+1
    # make some stuff
    # .....
    layer.SetFeature(feature)
    feature = layer.GetNextFeature()
layer.CommitTransaction()
source.Destroy()
print ("Finish",i,clock())

Compare the timings:

With one transaction

python run_a.py
('Start', 7.292488590901599e-07)
('Finish', 1612, 0.09405851784544883)

Original script without transaction:

python run_a.py
('Start', 7.292488590901599e-07)
('Finish', 1612, 15.54201567301648)

I have SSD disk which may explain why your original script is considerably faster for me (15.5 sec vs. 243.56 sec).

3
  • That looks good. This is exactly what I imagined. I searched for a transaction but found nothing. The same problem occurs when using "-skipfailure" in OGR2OGR: Will test it on Tuesday with the real data. Commented Apr 1, 2018 at 16:20
  • -skipfailures forces the size of a transaction into one row because in case of failure the whole transaction must be rolled back. If speed matters it is usually better to avoid -skipfailures and spend time for fixing the source data so that no failures will happen. GDAL transactions are documented in gdal.org/classOGRLayer.html#ac3cdf24212ec4719ade6065dcb63bb37.
    – user30184
    Commented Apr 1, 2018 at 16:33
  • +1 The explicit open/close transaction made a huge difference to my bulk inserts! (for some reason I thought OGR was already doing this. Ah well!)
    – winwaed
    Commented Sep 20, 2023 at 0:23
4

What is going on is that the features in the GeoPackage file are modified directly i.e. the code writes to the file when it is calling layer.SetFeature(feature) in the loop. What you should do is to create a copy of the layer to the memory, edit the features in the memory layer and copy the memory layer back to the GeoPackage file.

Test with the following code took about 4 seconds with the a.gpkg file:

# -*- coding: utf-8 -*-
from osgeo import ogr
from time import clock

print ("Start",clock())
source = ogr.Open("c:/.../a.gpkg",update=False)
layer = source.GetLayerByName( "263c6845-e2c3-4eee-ae26-f0e9539f3c2bP" )
laydef = layer.GetLayerDefn()

driver_mem = ogr.GetDriverByName('MEMORY')
source_mem = driver_mem.CreateDataSource('memData')
#open the memory datasource with write access
tmp_mem = driver_mem.Open('memData',update=True)
#copy a layer to memory
layer_mem = source_mem.CopyLayer(layer,'263c6845-e2c3-4eee-ae26-f0e9539f3c2bP',['OVERWRITE=YES'])

i=0
for feature in layer_mem:
    i=i+1
    # make some stuff
    # ....
    layer.SetFeature(feature)

source.Destroy()

driver_dest = ogr.GetDriverByName('GPKG')
source_dest = driver_dest.CreateDataSource('c:/.../a.gpkg')
tmp_dest = driver_dest.Open('c:/.../a.gpkg',update=True)
layer_dest = source_dest.CopyLayer(layer_mem,'263c6845-e2c3-4eee-ae26-f0e9539f3c2bP',['OVERWRITE=YES'])

source_mem.Destroy()
source_dest.Destroy()
print ("Finish",i,clock())
2
  • Thank you, that's a good idea. I will test it on Tuesday under real conditions. However, the statement "GeoPackage is the new shape format" is something wrong. With Shape I did not have to do any intermediate steps. Commented Mar 31, 2018 at 18:00
  • I agree that the GeoPackage file handling in various software needs to improve a lot but the Shape files that the statement probably refers to have 10 character size limitation for the table column names and also 2GB file size limitation that the GeoPackage files do not have and GeoPackage can also contain raster data. Commented Mar 31, 2018 at 19:23

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.