Skip to content

Commit 70ae301

Browse files
committed
[OGR provider] Update layer extent for GPKG layers
When moving or deleting a geometry that previously touched the layer extent, the layer extent was never shrinked. This fix requires GDAL 2.1.2 or above as well. Fixes #15273
1 parent 1b48a74 commit 70ae301

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

src/providers/ogr/qgsogrprovider.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
285285
, mFirstFieldIsFid( false )
286286
, ogrDataSource( nullptr )
287287
, mExtent( nullptr )
288+
, mForceRecomputeExtent( false )
288289
, ogrLayer( nullptr )
289290
, ogrOrigLayer( nullptr )
290291
, mLayerIndex( 0 )
@@ -481,7 +482,7 @@ bool QgsOgrProvider::setSubsetString( const QString& theSQL, bool updateFeatureC
481482
loadFields();
482483
QgsDebugMsg( "Done checking validity" );
483484

484-
updateExtents();
485+
invalidateCachedExtent( false );
485486

486487
emit dataChanged();
487488

@@ -976,6 +977,17 @@ QgsRectangle QgsOgrProvider::extent() const
976977
// get the extent_ (envelope) of the layer
977978
QgsDebugMsg( "Starting get extent" );
978979

980+
#if defined(GDAL_COMPUTE_VERSION) && GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,1,2)
981+
if ( mForceRecomputeExtent && mValid && ogrDriverName == "GPKG" && ogrDataSource && ogrOrigLayer )
982+
{
983+
QByteArray layerName = OGR_FD_GetName( OGR_L_GetLayerDefn( ogrOrigLayer ) );
984+
// works with unquoted layerName
985+
QByteArray sql = QByteArray( "RECOMPUTE EXTENT ON " ) + layerName;
986+
QgsDebugMsg( QString( "SQL: %1" ).arg( FROM8( sql ) ) );
987+
OGR_DS_ExecuteSQL( ogrDataSource, sql.constData(), nullptr, nullptr );
988+
}
989+
#endif
990+
979991
// TODO: This can be expensive, do we really need it!
980992
if ( ogrLayer == ogrOrigLayer )
981993
{
@@ -1019,6 +1031,12 @@ QgsRectangle QgsOgrProvider::extent() const
10191031

10201032
void QgsOgrProvider::updateExtents()
10211033
{
1034+
invalidateCachedExtent( true );
1035+
}
1036+
1037+
void QgsOgrProvider::invalidateCachedExtent( bool bForceRecomputeExtent )
1038+
{
1039+
mForceRecomputeExtent = bForceRecomputeExtent;
10221040
delete mExtent;
10231041
mExtent = nullptr;
10241042
}
@@ -1663,6 +1681,8 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
16631681
}
16641682
mShapefileMayBeCorrupted = true;
16651683

1684+
invalidateCachedExtent( true );
1685+
16661686
OGR_F_Destroy( theOGRFeature );
16671687
}
16681688
QgsOgrConnPool::instance()->invalidateConnections( dataSourceUri() );
@@ -1732,7 +1752,7 @@ bool QgsOgrProvider::deleteFeatures( const QgsFeatureIds & id )
17321752

17331753
clearMinMaxCache();
17341754

1735-
updateExtents();
1755+
invalidateCachedExtent( true );
17361756

17371757
return returnvalue;
17381758
}
@@ -3440,7 +3460,7 @@ void QgsOgrProvider::close()
34403460
mValid = false;
34413461
setProperty( "_debug_open_mode", "invalid" );
34423462

3443-
updateExtents();
3463+
invalidateCachedExtent( false );
34443464
}
34453465

34463466
void QgsOgrProvider::reloadData()

src/providers/ogr/qgsogrprovider.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ class QgsOgrProvider : public QgsVectorDataProvider
280280
/** Clean shapefile from features which are marked as deleted */
281281
void repack();
282282

283+
/** Invalidate extent and optionnaly force its low level recomputation */
284+
void invalidateCachedExtent( bool bForceRecomputeExtent );
285+
283286
enum OpenMode
284287
{
285288
OpenModeInitial,
@@ -299,6 +302,7 @@ class QgsOgrProvider : public QgsVectorDataProvider
299302
bool mFirstFieldIsFid;
300303
OGRDataSourceH ogrDataSource;
301304
mutable OGREnvelope* mExtent;
305+
bool mForceRecomputeExtent;
302306

303307
/** This member variable receives the same value as extent_
304308
in the method QgsOgrProvider::extent(). The purpose is to prevent a memory leak*/

tests/src/python/test_provider_ogr_gpkg.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import shutil
2020
from osgeo import gdal, ogr
2121

22-
from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry
22+
from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsRectangle
2323
from qgis.testing import start_app, unittest
2424

2525
start_app()
@@ -154,5 +154,44 @@ def testBug15351_commit_closeProvider_closeIter(self):
154154
def testBug15351_commit_closeIter_closeProvider(self):
155155
self.internalTestBug15351('commit_closeIter_closeProvider')
156156

157+
@unittest.expectedFailure(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 1, 2))
158+
def testGeopackageExtentUpdate(self):
159+
''' test http://hub.qgis.org/issues/15273 '''
160+
tmpfile = os.path.join(self.basetestpath, 'testGeopackageExtentUpdate.gpkg')
161+
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
162+
lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint)
163+
f = ogr.Feature(lyr.GetLayerDefn())
164+
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(0 0)'))
165+
lyr.CreateFeature(f)
166+
f = ogr.Feature(lyr.GetLayerDefn())
167+
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(1 1)'))
168+
lyr.CreateFeature(f)
169+
f = None
170+
f = ogr.Feature(lyr.GetLayerDefn())
171+
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(1 0.5)'))
172+
lyr.CreateFeature(f)
173+
f = None
174+
ds = None
175+
176+
vl = QgsVectorLayer(u'{}'.format(tmpfile), u'test', u'ogr')
177+
178+
# Test moving a geometry that touches the bbox
179+
self.assertTrue(vl.startEditing())
180+
self.assertTrue(vl.changeGeometry(1, QgsGeometry.fromWkt('Point (0.5 0)')))
181+
self.assertTrue(vl.commitChanges())
182+
reference = QgsGeometry.fromRect(QgsRectangle(0.5, 0.0, 1.0, 1.0))
183+
provider_extent = QgsGeometry.fromRect(vl.extent())
184+
self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001),
185+
provider_extent.asPolygon()[0])
186+
187+
# Test deleting a geometry that touches the bbox
188+
self.assertTrue(vl.startEditing())
189+
self.assertTrue(vl.deleteFeature(2))
190+
self.assertTrue(vl.commitChanges())
191+
reference = QgsGeometry.fromRect(QgsRectangle(0.5, 0.0, 1.0, 0.5))
192+
provider_extent = QgsGeometry.fromRect(vl.extent())
193+
self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001),
194+
provider_extent.asPolygon()[0])
195+
157196
if __name__ == '__main__':
158197
unittest.main()

0 commit comments

Comments
 (0)