Skip to content

Commit c28ad0b

Browse files
authoredApr 6, 2017
Merge pull request #4335 from nyalldawson/save_vector_with_extent
Much faster save as vector layer when using restricted extent
2 parents 25d9936 + d2f3eb1 commit c28ad0b

File tree

2 files changed

+99
-11
lines changed

2 files changed

+99
-11
lines changed
 

‎src/core/qgsvectorfilewriter.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "qgslocalec.h"
3333
#include "qgscsexception.h"
3434
#include "qgssettings.h"
35+
#include "qgsgeometryengine.h"
3536

3637
#include <QFile>
3738
#include <QFileInfo>
@@ -2398,6 +2399,34 @@ QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer *layer,
23982399
req.setSubsetOfAttributes( attributes );
23992400
if ( options.onlySelectedFeatures )
24002401
req.setFilterFids( layer->selectedFeatureIds() );
2402+
2403+
QgsGeometry filterRectGeometry;
2404+
std::unique_ptr< QgsGeometryEngine > filterRectEngine;
2405+
if ( !options.filterExtent.isNull() )
2406+
{
2407+
QgsRectangle filterRect = options.filterExtent;
2408+
bool useFilterRect = true;
2409+
if ( shallTransform )
2410+
{
2411+
try
2412+
{
2413+
// map filter rect back from destination CRS to layer CRS
2414+
filterRect = options.ct.transformBoundingBox( filterRect, QgsCoordinateTransform::ReverseTransform );
2415+
}
2416+
catch ( QgsCsException & )
2417+
{
2418+
useFilterRect = false;
2419+
}
2420+
}
2421+
if ( useFilterRect )
2422+
{
2423+
req.setFilterRect( filterRect );
2424+
}
2425+
filterRectGeometry = QgsGeometry::fromRect( options.filterExtent );
2426+
filterRectEngine.reset( QgsGeometry::createGeometryEngine( filterRectGeometry.geometry() ) );
2427+
filterRectEngine->prepareGeometry();
2428+
}
2429+
24012430
QgsFeatureIterator fit = layer->getFeatures( req );
24022431

24032432
//create symbol table if needed
@@ -2490,7 +2519,7 @@ QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer *layer,
24902519
}
24912520
}
24922521

2493-
if ( fet.hasGeometry() && !options.filterExtent.isNull() && !fet.geometry().intersects( options.filterExtent ) )
2522+
if ( fet.hasGeometry() && filterRectEngine && !filterRectEngine->intersects( *fet.geometry().geometry() ) )
24942523
continue;
24952524

24962525
if ( attributes.size() < 1 && options.skipAttributeCreation )

‎tests/src/python/test_qgsvectorfilewriter.py

100644100755
Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99
from builtins import next
1010
from builtins import str
11+
1112
__author__ = 'Tim Sutton'
1213
__date__ = '20/08/2012'
1314
__copyright__ = 'Copyright 2012, The QGIS Project'
@@ -24,15 +25,18 @@
2425
QgsCoordinateReferenceSystem,
2526
QgsVectorFileWriter,
2627
QgsFeatureRequest,
27-
QgsWkbTypes
28+
QgsWkbTypes,
29+
QgsRectangle,
30+
QgsCoordinateTransform
2831
)
2932
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir
3033
import os
3134
import osgeo.gdal # NOQA
3235
from osgeo import gdal, ogr
3336
from qgis.testing import start_app, unittest
34-
from utilities import writeShape, compareWkt
37+
from utilities import writeShape, compareWkt, unitTestDataPath
3538

39+
TEST_DATA_DIR = unitTestDataPath()
3640
start_app()
3741

3842

@@ -41,7 +45,6 @@ def GDAL_COMPUTE_VERSION(maj, min, rev):
4145

4246

4347
class TestFieldValueConverter(QgsVectorFileWriter.FieldValueConverter):
44-
4548
def __init__(self, layer):
4649
QgsVectorFileWriter.FieldValueConverter.__init__(self)
4750
self.layer = layer
@@ -66,7 +69,6 @@ def convert(self, idx, value):
6669

6770

6871
class TestQgsVectorFileWriter(unittest.TestCase):
69-
7072
mMemoryLayer = None
7173

7274
def testWrite(self):
@@ -142,7 +144,57 @@ def testDateTimeWriteShapefile(self):
142144
# shapefiles do not support datetime types
143145
datetime_idx = created_layer.fields().lookupField('dt_f')
144146
self.assertIsInstance(f.attributes()[datetime_idx], str)
145-
self.assertEqual(f.attributes()[datetime_idx], QDateTime(QDate(2014, 3, 5), QTime(13, 45, 22)).toString("yyyy/MM/dd hh:mm:ss.zzz"))
147+
self.assertEqual(f.attributes()[datetime_idx],
148+
QDateTime(QDate(2014, 3, 5), QTime(13, 45, 22)).toString("yyyy/MM/dd hh:mm:ss.zzz"))
149+
150+
def testWriterWithExtent(self):
151+
"""Check writing using extent filter."""
152+
source_file = os.path.join(TEST_DATA_DIR, 'points.shp')
153+
source_layer = QgsVectorLayer(source_file, 'Points', 'ogr')
154+
self.assertTrue(source_layer.isValid())
155+
156+
options = QgsVectorFileWriter.SaveVectorOptions()
157+
options.driverName = 'ESRI Shapefile'
158+
options.filterExtent = QgsRectangle(-111, 26, -96, 38)
159+
160+
dest_file_name = os.path.join(str(QDir.tempPath()), 'extent_no_transform.shp')
161+
write_result = QgsVectorFileWriter.writeAsVectorFormat(
162+
source_layer,
163+
dest_file_name,
164+
options)
165+
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
166+
167+
# Open result and check
168+
created_layer = QgsVectorLayer('{}|layerid=0'.format(dest_file_name), 'test', 'ogr')
169+
features = [f for f in created_layer.getFeatures()]
170+
self.assertEqual(len(features), 5)
171+
for f in features:
172+
self.assertTrue(f.geometry().intersects(options.filterExtent))
173+
174+
def testWriterWithExtentAndReprojection(self):
175+
"""Check writing using extent filter with reprojection."""
176+
source_file = os.path.join(TEST_DATA_DIR, 'points.shp')
177+
source_layer = QgsVectorLayer(source_file, 'Points', 'ogr')
178+
self.assertTrue(source_layer.isValid())
179+
180+
options = QgsVectorFileWriter.SaveVectorOptions()
181+
options.driverName = 'ESRI Shapefile'
182+
options.filterExtent = QgsRectangle(-12511460, 3045157, -10646621, 4683497)
183+
options.ct = QgsCoordinateTransform(source_layer.crs(), QgsCoordinateReferenceSystem.fromEpsgId(3785))
184+
185+
dest_file_name = os.path.join(str(QDir.tempPath()), 'extent_transform.shp')
186+
write_result = QgsVectorFileWriter.writeAsVectorFormat(
187+
source_layer,
188+
dest_file_name,
189+
options)
190+
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
191+
192+
# Open result and check
193+
created_layer = QgsVectorLayer('{}|layerid=0'.format(dest_file_name), 'test', 'ogr')
194+
features = [f for f in created_layer.getFeatures()]
195+
self.assertEqual(len(features), 5)
196+
for f in features:
197+
self.assertTrue(f.geometry().intersects(options.filterExtent))
146198

147199
def testDateTimeWriteTabfile(self):
148200
"""Check writing date and time fields to an MapInfo tabfile."""
@@ -236,12 +288,14 @@ def testWriteShapefileWithZ(self):
236288
g = f.geometry()
237289
wkt = g.exportToWkt()
238290
expWkt = 'PointZ (1 2 3)'
239-
self.assertTrue(compareWkt(expWkt, wkt), "saving geometry with Z failed: mismatch Expected:\n%s\nGot:\n%s\n" % (expWkt, wkt))
291+
self.assertTrue(compareWkt(expWkt, wkt),
292+
"saving geometry with Z failed: mismatch Expected:\n%s\nGot:\n%s\n" % (expWkt, wkt))
240293

241294
# also try saving out the shapefile version again, as an extra test
242295
# this tests that saving a layer with z WITHOUT explicitly telling the writer to keep z values,
243296
# will stay retain the z values
244-
dest_file_name = os.path.join(str(QDir.tempPath()), 'point_{}_copy.shp'.format(QgsWkbTypes.displayString(t)))
297+
dest_file_name = os.path.join(str(QDir.tempPath()),
298+
'point_{}_copy.shp'.format(QgsWkbTypes.displayString(t)))
245299
crs = QgsCoordinateReferenceSystem()
246300
crs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId)
247301
write_result = QgsVectorFileWriter.writeAsVectorFormat(
@@ -257,7 +311,8 @@ def testWriteShapefileWithZ(self):
257311
f = next(created_layer_from_shp.getFeatures(QgsFeatureRequest()))
258312
g = f.geometry()
259313
wkt = g.exportToWkt()
260-
self.assertTrue(compareWkt(expWkt, wkt), "saving geometry with Z failed: mismatch Expected:\n%s\nGot:\n%s\n" % (expWkt, wkt))
314+
self.assertTrue(compareWkt(expWkt, wkt),
315+
"saving geometry with Z failed: mismatch Expected:\n%s\nGot:\n%s\n" % (expWkt, wkt))
261316

262317
def testWriteShapefileWithMultiConversion(self):
263318
"""Check writing geometries to an ESRI shapefile with conversion to multi."""
@@ -296,7 +351,9 @@ def testWriteShapefileWithMultiConversion(self):
296351
g = f.geometry()
297352
wkt = g.exportToWkt()
298353
expWkt = 'MultiPoint ((1 2))'
299-
self.assertTrue(compareWkt(expWkt, wkt), "saving geometry with multi conversion failed: mismatch Expected:\n%s\nGot:\n%s\n" % (expWkt, wkt))
354+
self.assertTrue(compareWkt(expWkt, wkt),
355+
"saving geometry with multi conversion failed: mismatch Expected:\n%s\nGot:\n%s\n" % (
356+
expWkt, wkt))
300357

301358
def testWriteShapefileWithAttributeSubsets(self):
302359
"""Tests writing subsets of attributes to files."""
@@ -379,7 +436,9 @@ def testWriteShapefileWithAttributeSubsets(self):
379436
g = f.geometry()
380437
wkt = g.exportToWkt()
381438
expWkt = 'Point (1 2)'
382-
self.assertTrue(compareWkt(expWkt, wkt), "geometry not saved correctly when saving without attributes : mismatch Expected:\n%s\nGot:\n%s\n" % (expWkt, wkt))
439+
self.assertTrue(compareWkt(expWkt, wkt),
440+
"geometry not saved correctly when saving without attributes : mismatch Expected:\n%s\nGot:\n%s\n" % (
441+
expWkt, wkt))
383442
self.assertEqual(f['FID'], 0)
384443

385444
def testValueConverter(self):

0 commit comments

Comments
 (0)
Please sign in to comment.