Skip to content

Commit ffebfd9

Browse files
committed
[FEATURE] Allow overide of geometry type in vector save as dialog
Also allows forcing output file to be multi type, or include z dimension. This makes it possible to do things like save a geometryless table WITH a geometry type, so that geometries can then be manually added to rows. Previously this was only possible to do in QGIS by resorting to dummy joins or other hacks.
1 parent 09e7102 commit ffebfd9

File tree

7 files changed

+250
-30
lines changed

7 files changed

+250
-30
lines changed

python/core/qgsvectorfilewriter.sip

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ class QgsVectorFileWriter
121121
@param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
122122
@param symbologyExport symbology to export
123123
@param symbologyScale scale of symbology
124-
@param filterExtent if not a null pointer, only features intersecting the extent will be saved
124+
@param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4)
125+
allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14)
126+
@param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14)
127+
@param includeZ set to true to include z dimension in output. This option is only valid if overrideGeometryType is set. (added in QGIS 2.14)
125128
*/
126129
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
127130
const QString& fileName,
@@ -136,10 +139,33 @@ class QgsVectorFileWriter
136139
QString *newFilename = 0,
137140
SymbologyExport symbologyExport = NoSymbology,
138141
double symbologyScale = 1.0,
139-
const QgsRectangle* filterExtent = 0 // added in 2.4
142+
const QgsRectangle* filterExtent = 0,
143+
QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown,
144+
bool forceMulti = false,
145+
bool includeZ = false
140146
);
141147

142-
//! @note added in v2.2
148+
/** Writes a layer out to a vector file.
149+
* @param layer layer to write
150+
* @param fileName file name to write to
151+
* @param fileEncoding encoding to use
152+
* @param ct
153+
* @param driverName OGR driver to use
154+
* @param onlySelected write only selected features of layer
155+
* @param errorMessage pointer to buffer fo error message
156+
* @param datasourceOptions list of OGR data source creation options
157+
* @param layerOptions list of OGR layer creation options
158+
* @param skipAttributeCreation only write geometries
159+
* @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
160+
* @param symbologyExport symbology to export
161+
* @param symbologyScale scale of symbology
162+
* @param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4)
163+
* @param overrideGeometryType set to a valid geometry type to override the default geometry type for the layer. This parameter
164+
* allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14)
165+
* @param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14)
166+
* @param includeZ set to true to include z dimension in output. This option is only valid if overrideGeometryType is set. (added in QGIS 2.14)
167+
* @note added in v2.2
168+
*/
143169
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
144170
const QString& fileName,
145171
const QString& fileEncoding,
@@ -153,7 +179,10 @@ class QgsVectorFileWriter
153179
QString *newFilename = 0,
154180
SymbologyExport symbologyExport = NoSymbology,
155181
double symbologyScale = 1.0,
156-
const QgsRectangle* filterExtent = 0 // added in 2.4
182+
const QgsRectangle* filterExtent = 0,
183+
QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown,
184+
bool forceMulti = false,
185+
bool includeZ = false
157186
);
158187

159188
/** Create shapefile and initialize it */

src/app/ogr/qgsvectorlayersaveasdialog.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ void QgsVectorLayerSaveAsDialog::setup()
6767
mFormatComboBox->setCurrentIndex( mFormatComboBox->findData( format ) );
6868
mFormatComboBox->blockSignals( false );
6969

70+
//add geometry types to combobox
71+
mGeometryTypeComboBox->addItem( tr( "Automatic" ), -1 );
72+
mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::Point ), QgsWKBTypes::Point );
73+
mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::LineString ), QgsWKBTypes::LineString );
74+
mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::Polygon ), QgsWKBTypes::Polygon );
75+
mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::GeometryCollection ), QgsWKBTypes::GeometryCollection );
76+
mGeometryTypeComboBox->addItem( tr( "No geometry" ), QgsWKBTypes::NoGeometry );
77+
mGeometryTypeComboBox->setCurrentIndex( mGeometryTypeComboBox->findData( -1 ) );
78+
7079
mEncodingComboBox->addItems( QgsVectorDataProvider::availableEncodings() );
7180

7281
QString enc = settings.value( "/UI/encoding", "System" ).toString();
@@ -458,6 +467,44 @@ bool QgsVectorLayerSaveAsDialog::onlySelected() const
458467
return mSelectedOnly->isChecked();
459468
}
460469

470+
QgsWKBTypes::Type QgsVectorLayerSaveAsDialog::geometryType() const
471+
{
472+
int currentIndexData = mGeometryTypeComboBox->itemData( mGeometryTypeComboBox->currentIndex() ).toInt();
473+
if ( currentIndexData == -1 )
474+
{
475+
//automatic
476+
return QgsWKBTypes::Unknown;
477+
}
478+
479+
return ( QgsWKBTypes::Type )currentIndexData;
480+
}
481+
482+
bool QgsVectorLayerSaveAsDialog::automaticGeometryType() const
483+
{
484+
int currentIndexData = mGeometryTypeComboBox->itemData( mGeometryTypeComboBox->currentIndex() ).toInt();
485+
return currentIndexData == -1;
486+
}
487+
488+
bool QgsVectorLayerSaveAsDialog::forceMulti() const
489+
{
490+
return mForceMultiCheckBox->isChecked();
491+
}
492+
493+
void QgsVectorLayerSaveAsDialog::setForceMulti( bool checked )
494+
{
495+
mForceMultiCheckBox->setChecked( checked );
496+
}
497+
498+
bool QgsVectorLayerSaveAsDialog::includeZ() const
499+
{
500+
return mIncludeZCheckBox->isChecked();
501+
}
502+
503+
void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked )
504+
{
505+
mIncludeZCheckBox->setChecked( checked );
506+
}
507+
461508
void QgsVectorLayerSaveAsDialog::on_mSymbologyExportComboBox_currentIndexChanged( const QString& text )
462509
{
463510
bool scaleEnabled = true;
@@ -468,3 +515,11 @@ void QgsVectorLayerSaveAsDialog::on_mSymbologyExportComboBox_currentIndexChanged
468515
mScaleSpinBox->setEnabled( scaleEnabled );
469516
mScaleLabel->setEnabled( scaleEnabled );
470517
}
518+
519+
void QgsVectorLayerSaveAsDialog::on_mGeometryTypeComboBox_currentIndexChanged( int index )
520+
{
521+
int currentIndexData = mGeometryTypeComboBox->itemData( index ).toInt();
522+
523+
mForceMultiCheckBox->setEnabled( currentIndexData != -1 );
524+
mIncludeZCheckBox->setEnabled( currentIndexData != -1 );
525+
}

src/app/ogr/qgsvectorlayersaveasdialog.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,44 @@ class QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVectorLayerSav
6565

6666
bool onlySelected() const;
6767

68+
/** Returns the selected flat geometry type for the export.
69+
* @see automaticGeometryType()
70+
* @see forceMulti()
71+
* @see includeZ()
72+
*/
73+
QgsWKBTypes::Type geometryType() const;
74+
75+
/** Returns true if geometry type is set to automatic.
76+
* @see geometryType()
77+
*/
78+
bool automaticGeometryType() const;
79+
80+
/** Returns true if force multi geometry type is checked.
81+
* @see includeZ()
82+
*/
83+
bool forceMulti() const;
84+
85+
/** Sets whether the force multi geometry checkbox should be checked.
86+
*/
87+
void setForceMulti( bool checked );
88+
89+
/** Returns true if include z dimension is checked.
90+
* @see forceMulti()
91+
*/
92+
bool includeZ() const;
93+
94+
/** Sets whether the include z dimension checkbox should be checked.
95+
*/
96+
void setIncludeZ( bool checked );
97+
6898
private slots:
6999
void on_mFormatComboBox_currentIndexChanged( int idx );
70100
void on_leFilename_textChanged( const QString& text );
71101
void on_browseFilename_clicked();
72102
void on_mCrsSelector_crsChanged( const QgsCoordinateReferenceSystem& crs );
73103
void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); }
74104
void on_mSymbologyExportComboBox_currentIndexChanged( const QString& text );
105+
void on_mGeometryTypeComboBox_currentIndexChanged( int index );
75106
void accept() override;
76107

77108
private:

src/app/qgisapp.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5476,13 +5476,16 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
54765476
QgsVectorLayerSaveAsDialog *dialog = new QgsVectorLayerSaveAsDialog( vlayer->crs().srsid(), vlayer->extent(), vlayer->selectedFeatureCount() != 0, options, this );
54775477

54785478
dialog->setCanvasExtent( mMapCanvas->mapSettings().visibleExtent(), mMapCanvas->mapSettings().destinationCrs() );
5479+
dialog->setIncludeZ( QgsWKBTypes::hasZ( QGis::fromOldWkbType( vlayer->wkbType() ) ) );
54795480

54805481
if ( dialog->exec() == QDialog::Accepted )
54815482
{
54825483
QString encoding = dialog->encoding();
54835484
QString vectorFilename = dialog->filename();
54845485
QString format = dialog->format();
54855486
QStringList datasourceOptions = dialog->datasourceOptions();
5487+
bool autoGeometryType = dialog->automaticGeometryType();
5488+
QgsWKBTypes::Type forcedGeometryType = dialog->geometryType();
54865489

54875490
QgsCoordinateTransform* ct = 0;
54885491
destCRS = QgsCoordinateReferenceSystem( dialog->crs(), QgsCoordinateReferenceSystem::InternalCrsId );
@@ -5529,7 +5532,11 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
55295532
&newFilename,
55305533
( QgsVectorFileWriter::SymbologyExport )( dialog->symbologyExport() ),
55315534
dialog->scaleDenominator(),
5532-
dialog->hasFilterExtent() ? &filterExtent : 0 );
5535+
dialog->hasFilterExtent() ? &filterExtent : 0,
5536+
autoGeometryType ? QgsWKBTypes::Unknown : forcedGeometryType,
5537+
dialog->forceMulti(),
5538+
dialog->includeZ()
5539+
);
55335540

55345541
delete ct;
55355542

src/core/qgsvectorfilewriter.cpp

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,7 +1725,8 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature )
17251725
QgsGeometry *geom = feature.geometry();
17261726

17271727
// turn single geoemetry to multi geometry if needed
1728-
if ( geom && geom->wkbType() != mWkbType && geom->wkbType() == QGis::singleType( mWkbType ) )
1728+
if ( geom && geom->wkbType() != mWkbType &&
1729+
QgsWKBTypes::flatType( geom->geometry()->wkbType() ) == QgsWKBTypes::flatType( QgsWKBTypes::singleType( QGis::fromOldWkbType( mWkbType ) ) ) )
17291730
{
17301731
geom->convertToMultiType();
17311732
}
@@ -1738,6 +1739,10 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature )
17381739
// we must force the use of 25D.
17391740
if ( mWkbType >= QGis::WKBPoint25D && mWkbType <= QGis::WKBMultiPolygon25D )
17401741
{
1742+
//ND: I suspect there's a bug here, in that this is NOT converting the geometry's WKB type,
1743+
//so the exported WKB has a different type to what the OGRGeometry is expecting.
1744+
//possibly this is handled already in OGR, but it should be fixed regardless by actually converting
1745+
//geom to the correct WKB type
17411746
QgsWKBTypes::Type wkbType = QGis::fromOldWkbType( geom->wkbType() );
17421747
if ( wkbType >= QgsWKBTypes::PointZ && wkbType <= QgsWKBTypes::MultiPolygonZ )
17431748
{
@@ -1799,6 +1804,10 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature )
17991804
// set geometry (ownership is not passed to OGR)
18001805
OGR_F_SetGeometry( poFeature, mGeom );
18011806
}
1807+
else
1808+
{
1809+
OGR_F_SetGeometry( poFeature, createEmptyGeometry( mWkbType ) );
1810+
}
18021811
}
18031812
return poFeature;
18041813
}
@@ -1848,7 +1857,10 @@ QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer,
18481857
QString *newFilename,
18491858
SymbologyExport symbologyExport,
18501859
double symbologyScale,
1851-
const QgsRectangle* filterExtent )
1860+
const QgsRectangle* filterExtent,
1861+
QgsWKBTypes::Type overrideGeometryType,
1862+
bool forceMulti,
1863+
bool includeZ )
18521864
{
18531865
QgsCoordinateTransform* ct = 0;
18541866
if ( destCRS && layer )
@@ -1857,7 +1869,7 @@ QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer,
18571869
}
18581870

18591871
QgsVectorFileWriter::WriterError error = writeAsVectorFormat( layer, fileName, fileEncoding, ct, driverName, onlySelected,
1860-
errorMessage, datasourceOptions, layerOptions, skipAttributeCreation, newFilename, symbologyExport, symbologyScale, filterExtent );
1872+
errorMessage, datasourceOptions, layerOptions, skipAttributeCreation, newFilename, symbologyExport, symbologyScale, filterExtent, overrideGeometryType, forceMulti, includeZ );
18611873
delete ct;
18621874
return error;
18631875
}
@@ -1875,7 +1887,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
18751887
QString *newFilename,
18761888
SymbologyExport symbologyExport,
18771889
double symbologyScale,
1878-
const QgsRectangle* filterExtent )
1890+
const QgsRectangle* filterExtent,
1891+
QgsWKBTypes::Type overrideGeometryType,
1892+
bool forceMulti,
1893+
bool includeZ )
18791894
{
18801895
if ( !layer )
18811896
{
@@ -1896,7 +1911,18 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
18961911
outputCRS = &layer->crs();
18971912
}
18981913

1899-
QGis::WkbType wkbType = layer->wkbType();
1914+
QgsWKBTypes::Type destWkbType = QGis::fromOldWkbType( layer->wkbType() );
1915+
if ( overrideGeometryType != QgsWKBTypes::Unknown )
1916+
{
1917+
destWkbType = QgsWKBTypes::flatType( overrideGeometryType );
1918+
if ( QgsWKBTypes::hasZ( overrideGeometryType ) || includeZ )
1919+
destWkbType = QgsWKBTypes::addZ( destWkbType );
1920+
}
1921+
if ( forceMulti )
1922+
{
1923+
destWkbType = QgsWKBTypes::multiType( destWkbType );
1924+
}
1925+
19001926
QgsFields fields = skipAttributeCreation ? QgsFields() : layer->fields();
19011927

19021928
if ( layer->providerType() == "ogr" && layer->dataProvider() )
@@ -1912,19 +1938,21 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
19121938
}
19131939

19141940
// Shapefiles might contain multi types although wkbType() only reports singles
1915-
if ( layer->storageType() == "ESRI Shapefile" )
1941+
if ( layer->storageType() == "ESRI Shapefile" && !QgsWKBTypes::isMultiType( destWkbType ) )
19161942
{
1917-
const QgsFeatureIds &ids = layer->selectedFeaturesIds();
1918-
QgsFeatureIterator fit = layer->getFeatures();
1943+
QgsFeatureRequest req;
1944+
if ( onlySelected )
1945+
{
1946+
req.setFilterFids( layer->selectedFeaturesIds() );
1947+
}
1948+
QgsFeatureIterator fit = layer->getFeatures( req );
19191949
QgsFeature fet;
1950+
19201951
while ( fit.nextFeature( fet ) )
19211952
{
1922-
if ( onlySelected && !ids.contains( fet.id() ) )
1923-
continue;
1924-
1925-
if ( fet.constGeometry() && fet.constGeometry()->wkbType() == QGis::multiType( wkbType ) )
1953+
if ( fet.constGeometry() && !fet.constGeometry()->isEmpty() && QgsWKBTypes::isMultiType( fet.constGeometry()->geometry()->wkbType() ) )
19261954
{
1927-
wkbType = QGis::multiType( wkbType );
1955+
destWkbType = QgsWKBTypes::multiType( destWkbType );
19281956
break;
19291957
}
19301958
}
@@ -1947,7 +1975,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
19471975
}
19481976

19491977
QgsVectorFileWriter* writer =
1950-
new QgsVectorFileWriter( fileName, fileEncoding, fields, wkbType, outputCRS, driverName, datasourceOptions, layerOptions, newFilename, symbologyExport );
1978+
new QgsVectorFileWriter( fileName, fileEncoding, fields, QGis::fromNewWkbType( destWkbType ), outputCRS, driverName, datasourceOptions, layerOptions, newFilename, symbologyExport );
19511979
writer->setSymbologyScaleDenominator( symbologyScale );
19521980

19531981
if ( newFilename )

src/core/qgsvectorfilewriter.h

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,11 @@ class CORE_EXPORT QgsVectorFileWriter
174174
@param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
175175
@param symbologyExport symbology to export
176176
@param symbologyScale scale of symbology
177-
@param filterExtent if not a null pointer, only features intersecting the extent will be saved
177+
@param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4)
178+
@param overrideGeometryType set to a valid geometry type to override the default geometry type for the layer. This parameter
179+
allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14)
180+
@param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14)
181+
@param includeZ set to true to include z dimension in output. This option is only valid if overrideGeometryType is set. (added in QGIS 2.14)
178182
*/
179183
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
180184
const QString& fileName,
@@ -189,10 +193,33 @@ class CORE_EXPORT QgsVectorFileWriter
189193
QString *newFilename = 0,
190194
SymbologyExport symbologyExport = NoSymbology,
191195
double symbologyScale = 1.0,
192-
const QgsRectangle* filterExtent = 0 // added in 2.4
196+
const QgsRectangle* filterExtent = 0,
197+
QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown,
198+
bool forceMulti = false,
199+
bool includeZ = false
193200
);
194201

195-
//! @note added in v2.2
202+
/** Writes a layer out to a vector file.
203+
* @param layer layer to write
204+
* @param fileName file name to write to
205+
* @param fileEncoding encoding to use
206+
* @param ct
207+
* @param driverName OGR driver to use
208+
* @param onlySelected write only selected features of layer
209+
* @param errorMessage pointer to buffer fo error message
210+
* @param datasourceOptions list of OGR data source creation options
211+
* @param layerOptions list of OGR layer creation options
212+
* @param skipAttributeCreation only write geometries
213+
* @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
214+
* @param symbologyExport symbology to export
215+
* @param symbologyScale scale of symbology
216+
* @param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4)
217+
* @param overrideGeometryType set to a valid geometry type to override the default geometry type for the layer. This parameter
218+
* allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14)
219+
* @param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14)
220+
* @param includeZ set to true to include z dimension in output. This option is only valid if overrideGeometryType is set. (added in QGIS 2.14)
221+
* @note added in v2.2
222+
*/
196223
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
197224
const QString& fileName,
198225
const QString& fileEncoding,
@@ -206,7 +233,10 @@ class CORE_EXPORT QgsVectorFileWriter
206233
QString *newFilename = 0,
207234
SymbologyExport symbologyExport = NoSymbology,
208235
double symbologyScale = 1.0,
209-
const QgsRectangle* filterExtent = 0 // added in 2.4
236+
const QgsRectangle* filterExtent = 0,
237+
QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown,
238+
bool forceMulti = false,
239+
bool includeZ = false
210240
);
211241

212242
/** Create shapefile and initialize it */

0 commit comments

Comments
 (0)