diff --git a/python/core/qgsvectorfilewriter.sip b/python/core/qgsvectorfilewriter.sip index 00f250a5d1c..6f095d6c308 100644 --- a/python/core/qgsvectorfilewriter.sip +++ b/python/core/qgsvectorfilewriter.sip @@ -121,7 +121,10 @@ class QgsVectorFileWriter @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName). @param symbologyExport symbology to export @param symbologyScale scale of symbology - @param filterExtent if not a null pointer, only features intersecting the extent will be saved + @param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4) + allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14) + @param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14) + @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) */ static WriterError writeAsVectorFormat( QgsVectorLayer* layer, const QString& fileName, @@ -136,10 +139,33 @@ class QgsVectorFileWriter QString *newFilename = 0, SymbologyExport symbologyExport = NoSymbology, double symbologyScale = 1.0, - const QgsRectangle* filterExtent = 0 // added in 2.4 + const QgsRectangle* filterExtent = 0, + QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown, + bool forceMulti = false, + bool includeZ = false ); - //! @note added in v2.2 + /** Writes a layer out to a vector file. + * @param layer layer to write + * @param fileName file name to write to + * @param fileEncoding encoding to use + * @param ct + * @param driverName OGR driver to use + * @param onlySelected write only selected features of layer + * @param errorMessage pointer to buffer fo error message + * @param datasourceOptions list of OGR data source creation options + * @param layerOptions list of OGR layer creation options + * @param skipAttributeCreation only write geometries + * @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName). + * @param symbologyExport symbology to export + * @param symbologyScale scale of symbology + * @param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4) + * @param overrideGeometryType set to a valid geometry type to override the default geometry type for the layer. This parameter + * allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14) + * @param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14) + * @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) + * @note added in v2.2 + */ static WriterError writeAsVectorFormat( QgsVectorLayer* layer, const QString& fileName, const QString& fileEncoding, @@ -153,7 +179,10 @@ class QgsVectorFileWriter QString *newFilename = 0, SymbologyExport symbologyExport = NoSymbology, double symbologyScale = 1.0, - const QgsRectangle* filterExtent = 0 // added in 2.4 + const QgsRectangle* filterExtent = 0, + QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown, + bool forceMulti = false, + bool includeZ = false ); /** Create shapefile and initialize it */ diff --git a/src/app/ogr/qgsvectorlayersaveasdialog.cpp b/src/app/ogr/qgsvectorlayersaveasdialog.cpp index 83bd665488c..32a8221d9ea 100644 --- a/src/app/ogr/qgsvectorlayersaveasdialog.cpp +++ b/src/app/ogr/qgsvectorlayersaveasdialog.cpp @@ -67,6 +67,15 @@ void QgsVectorLayerSaveAsDialog::setup() mFormatComboBox->setCurrentIndex( mFormatComboBox->findData( format ) ); mFormatComboBox->blockSignals( false ); + //add geometry types to combobox + mGeometryTypeComboBox->addItem( tr( "Automatic" ), -1 ); + mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::Point ), QgsWKBTypes::Point ); + mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::LineString ), QgsWKBTypes::LineString ); + mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::Polygon ), QgsWKBTypes::Polygon ); + mGeometryTypeComboBox->addItem( QgsWKBTypes::displayString( QgsWKBTypes::GeometryCollection ), QgsWKBTypes::GeometryCollection ); + mGeometryTypeComboBox->addItem( tr( "No geometry" ), QgsWKBTypes::NoGeometry ); + mGeometryTypeComboBox->setCurrentIndex( mGeometryTypeComboBox->findData( -1 ) ); + mEncodingComboBox->addItems( QgsVectorDataProvider::availableEncodings() ); QString enc = settings.value( "/UI/encoding", "System" ).toString(); @@ -458,6 +467,44 @@ bool QgsVectorLayerSaveAsDialog::onlySelected() const return mSelectedOnly->isChecked(); } +QgsWKBTypes::Type QgsVectorLayerSaveAsDialog::geometryType() const +{ + int currentIndexData = mGeometryTypeComboBox->itemData( mGeometryTypeComboBox->currentIndex() ).toInt(); + if ( currentIndexData == -1 ) + { + //automatic + return QgsWKBTypes::Unknown; + } + + return ( QgsWKBTypes::Type )currentIndexData; +} + +bool QgsVectorLayerSaveAsDialog::automaticGeometryType() const +{ + int currentIndexData = mGeometryTypeComboBox->itemData( mGeometryTypeComboBox->currentIndex() ).toInt(); + return currentIndexData == -1; +} + +bool QgsVectorLayerSaveAsDialog::forceMulti() const +{ + return mForceMultiCheckBox->isChecked(); +} + +void QgsVectorLayerSaveAsDialog::setForceMulti( bool checked ) +{ + mForceMultiCheckBox->setChecked( checked ); +} + +bool QgsVectorLayerSaveAsDialog::includeZ() const +{ + return mIncludeZCheckBox->isChecked(); +} + +void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked ) +{ + mIncludeZCheckBox->setChecked( checked ); +} + void QgsVectorLayerSaveAsDialog::on_mSymbologyExportComboBox_currentIndexChanged( const QString& text ) { bool scaleEnabled = true; @@ -468,3 +515,11 @@ void QgsVectorLayerSaveAsDialog::on_mSymbologyExportComboBox_currentIndexChanged mScaleSpinBox->setEnabled( scaleEnabled ); mScaleLabel->setEnabled( scaleEnabled ); } + +void QgsVectorLayerSaveAsDialog::on_mGeometryTypeComboBox_currentIndexChanged( int index ) +{ + int currentIndexData = mGeometryTypeComboBox->itemData( index ).toInt(); + + mForceMultiCheckBox->setEnabled( currentIndexData != -1 ); + mIncludeZCheckBox->setEnabled( currentIndexData != -1 ); +} diff --git a/src/app/ogr/qgsvectorlayersaveasdialog.h b/src/app/ogr/qgsvectorlayersaveasdialog.h index bd8565dc076..a4c78c457a3 100644 --- a/src/app/ogr/qgsvectorlayersaveasdialog.h +++ b/src/app/ogr/qgsvectorlayersaveasdialog.h @@ -65,6 +65,36 @@ class QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVectorLayerSav bool onlySelected() const; + /** Returns the selected flat geometry type for the export. + * @see automaticGeometryType() + * @see forceMulti() + * @see includeZ() + */ + QgsWKBTypes::Type geometryType() const; + + /** Returns true if geometry type is set to automatic. + * @see geometryType() + */ + bool automaticGeometryType() const; + + /** Returns true if force multi geometry type is checked. + * @see includeZ() + */ + bool forceMulti() const; + + /** Sets whether the force multi geometry checkbox should be checked. + */ + void setForceMulti( bool checked ); + + /** Returns true if include z dimension is checked. + * @see forceMulti() + */ + bool includeZ() const; + + /** Sets whether the include z dimension checkbox should be checked. + */ + void setIncludeZ( bool checked ); + private slots: void on_mFormatComboBox_currentIndexChanged( int idx ); void on_leFilename_textChanged( const QString& text ); @@ -72,6 +102,7 @@ class QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVectorLayerSav void on_mCrsSelector_crsChanged( const QgsCoordinateReferenceSystem& crs ); void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } void on_mSymbologyExportComboBox_currentIndexChanged( const QString& text ); + void on_mGeometryTypeComboBox_currentIndexChanged( int index ); void accept() override; private: diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 3517315666e..eee03affe37 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -5476,6 +5476,7 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt QgsVectorLayerSaveAsDialog *dialog = new QgsVectorLayerSaveAsDialog( vlayer->crs().srsid(), vlayer->extent(), vlayer->selectedFeatureCount() != 0, options, this ); dialog->setCanvasExtent( mMapCanvas->mapSettings().visibleExtent(), mMapCanvas->mapSettings().destinationCrs() ); + dialog->setIncludeZ( QgsWKBTypes::hasZ( QGis::fromOldWkbType( vlayer->wkbType() ) ) ); if ( dialog->exec() == QDialog::Accepted ) { @@ -5483,6 +5484,8 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt QString vectorFilename = dialog->filename(); QString format = dialog->format(); QStringList datasourceOptions = dialog->datasourceOptions(); + bool autoGeometryType = dialog->automaticGeometryType(); + QgsWKBTypes::Type forcedGeometryType = dialog->geometryType(); QgsCoordinateTransform* ct = 0; destCRS = QgsCoordinateReferenceSystem( dialog->crs(), QgsCoordinateReferenceSystem::InternalCrsId ); @@ -5529,7 +5532,11 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt &newFilename, ( QgsVectorFileWriter::SymbologyExport )( dialog->symbologyExport() ), dialog->scaleDenominator(), - dialog->hasFilterExtent() ? &filterExtent : 0 ); + dialog->hasFilterExtent() ? &filterExtent : 0, + autoGeometryType ? QgsWKBTypes::Unknown : forcedGeometryType, + dialog->forceMulti(), + dialog->includeZ() + ); delete ct; diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp index a8b482be9b1..3f4a7bbd7d4 100644 --- a/src/core/qgsvectorfilewriter.cpp +++ b/src/core/qgsvectorfilewriter.cpp @@ -1725,7 +1725,8 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature ) QgsGeometry *geom = feature.geometry(); // turn single geoemetry to multi geometry if needed - if ( geom && geom->wkbType() != mWkbType && geom->wkbType() == QGis::singleType( mWkbType ) ) + if ( geom && geom->wkbType() != mWkbType && + QgsWKBTypes::flatType( geom->geometry()->wkbType() ) == QgsWKBTypes::flatType( QgsWKBTypes::singleType( QGis::fromOldWkbType( mWkbType ) ) ) ) { geom->convertToMultiType(); } @@ -1738,6 +1739,10 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature ) // we must force the use of 25D. if ( mWkbType >= QGis::WKBPoint25D && mWkbType <= QGis::WKBMultiPolygon25D ) { + //ND: I suspect there's a bug here, in that this is NOT converting the geometry's WKB type, + //so the exported WKB has a different type to what the OGRGeometry is expecting. + //possibly this is handled already in OGR, but it should be fixed regardless by actually converting + //geom to the correct WKB type QgsWKBTypes::Type wkbType = QGis::fromOldWkbType( geom->wkbType() ); if ( wkbType >= QgsWKBTypes::PointZ && wkbType <= QgsWKBTypes::MultiPolygonZ ) { @@ -1799,6 +1804,10 @@ OGRFeatureH QgsVectorFileWriter::createFeature( QgsFeature& feature ) // set geometry (ownership is not passed to OGR) OGR_F_SetGeometry( poFeature, mGeom ); } + else + { + OGR_F_SetGeometry( poFeature, createEmptyGeometry( mWkbType ) ); + } } return poFeature; } @@ -1848,7 +1857,10 @@ QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer, QString *newFilename, SymbologyExport symbologyExport, double symbologyScale, - const QgsRectangle* filterExtent ) + const QgsRectangle* filterExtent, + QgsWKBTypes::Type overrideGeometryType, + bool forceMulti, + bool includeZ ) { QgsCoordinateTransform* ct = 0; if ( destCRS && layer ) @@ -1857,7 +1869,7 @@ QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer, } QgsVectorFileWriter::WriterError error = writeAsVectorFormat( layer, fileName, fileEncoding, ct, driverName, onlySelected, - errorMessage, datasourceOptions, layerOptions, skipAttributeCreation, newFilename, symbologyExport, symbologyScale, filterExtent ); + errorMessage, datasourceOptions, layerOptions, skipAttributeCreation, newFilename, symbologyExport, symbologyScale, filterExtent, overrideGeometryType, forceMulti, includeZ ); delete ct; return error; } @@ -1875,7 +1887,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe QString *newFilename, SymbologyExport symbologyExport, double symbologyScale, - const QgsRectangle* filterExtent ) + const QgsRectangle* filterExtent, + QgsWKBTypes::Type overrideGeometryType, + bool forceMulti, + bool includeZ ) { if ( !layer ) { @@ -1896,7 +1911,18 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe outputCRS = &layer->crs(); } - QGis::WkbType wkbType = layer->wkbType(); + QgsWKBTypes::Type destWkbType = QGis::fromOldWkbType( layer->wkbType() ); + if ( overrideGeometryType != QgsWKBTypes::Unknown ) + { + destWkbType = QgsWKBTypes::flatType( overrideGeometryType ); + if ( QgsWKBTypes::hasZ( overrideGeometryType ) || includeZ ) + destWkbType = QgsWKBTypes::addZ( destWkbType ); + } + if ( forceMulti ) + { + destWkbType = QgsWKBTypes::multiType( destWkbType ); + } + QgsFields fields = skipAttributeCreation ? QgsFields() : layer->fields(); if ( layer->providerType() == "ogr" && layer->dataProvider() ) @@ -1912,19 +1938,21 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe } // Shapefiles might contain multi types although wkbType() only reports singles - if ( layer->storageType() == "ESRI Shapefile" ) + if ( layer->storageType() == "ESRI Shapefile" && !QgsWKBTypes::isMultiType( destWkbType ) ) { - const QgsFeatureIds &ids = layer->selectedFeaturesIds(); - QgsFeatureIterator fit = layer->getFeatures(); + QgsFeatureRequest req; + if ( onlySelected ) + { + req.setFilterFids( layer->selectedFeaturesIds() ); + } + QgsFeatureIterator fit = layer->getFeatures( req ); QgsFeature fet; + while ( fit.nextFeature( fet ) ) { - if ( onlySelected && !ids.contains( fet.id() ) ) - continue; - - if ( fet.constGeometry() && fet.constGeometry()->wkbType() == QGis::multiType( wkbType ) ) + if ( fet.constGeometry() && !fet.constGeometry()->isEmpty() && QgsWKBTypes::isMultiType( fet.constGeometry()->geometry()->wkbType() ) ) { - wkbType = QGis::multiType( wkbType ); + destWkbType = QgsWKBTypes::multiType( destWkbType ); break; } } @@ -1947,7 +1975,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe } QgsVectorFileWriter* writer = - new QgsVectorFileWriter( fileName, fileEncoding, fields, wkbType, outputCRS, driverName, datasourceOptions, layerOptions, newFilename, symbologyExport ); + new QgsVectorFileWriter( fileName, fileEncoding, fields, QGis::fromNewWkbType( destWkbType ), outputCRS, driverName, datasourceOptions, layerOptions, newFilename, symbologyExport ); writer->setSymbologyScaleDenominator( symbologyScale ); if ( newFilename ) diff --git a/src/core/qgsvectorfilewriter.h b/src/core/qgsvectorfilewriter.h index 4ccdb9b8a54..5879333105d 100644 --- a/src/core/qgsvectorfilewriter.h +++ b/src/core/qgsvectorfilewriter.h @@ -174,7 +174,11 @@ class CORE_EXPORT QgsVectorFileWriter @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName). @param symbologyExport symbology to export @param symbologyScale scale of symbology - @param filterExtent if not a null pointer, only features intersecting the extent will be saved + @param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4) + @param overrideGeometryType set to a valid geometry type to override the default geometry type for the layer. This parameter + allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14) + @param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14) + @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) */ static WriterError writeAsVectorFormat( QgsVectorLayer* layer, const QString& fileName, @@ -189,10 +193,33 @@ class CORE_EXPORT QgsVectorFileWriter QString *newFilename = 0, SymbologyExport symbologyExport = NoSymbology, double symbologyScale = 1.0, - const QgsRectangle* filterExtent = 0 // added in 2.4 + const QgsRectangle* filterExtent = 0, + QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown, + bool forceMulti = false, + bool includeZ = false ); - //! @note added in v2.2 + /** Writes a layer out to a vector file. + * @param layer layer to write + * @param fileName file name to write to + * @param fileEncoding encoding to use + * @param ct + * @param driverName OGR driver to use + * @param onlySelected write only selected features of layer + * @param errorMessage pointer to buffer fo error message + * @param datasourceOptions list of OGR data source creation options + * @param layerOptions list of OGR layer creation options + * @param skipAttributeCreation only write geometries + * @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName). + * @param symbologyExport symbology to export + * @param symbologyScale scale of symbology + * @param filterExtent if not a null pointer, only features intersecting the extent will be saved (added in QGIS 2.4) + * @param overrideGeometryType set to a valid geometry type to override the default geometry type for the layer. This parameter + * allows for conversion of geometryless tables to null geometries, etc (added in QGIS 2.14) + * @param forceMulti set to true to force creation of multi* geometries (added in QGIS 2.14) + * @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) + * @note added in v2.2 + */ static WriterError writeAsVectorFormat( QgsVectorLayer* layer, const QString& fileName, const QString& fileEncoding, @@ -206,7 +233,10 @@ class CORE_EXPORT QgsVectorFileWriter QString *newFilename = 0, SymbologyExport symbologyExport = NoSymbology, double symbologyScale = 1.0, - const QgsRectangle* filterExtent = 0 // added in 2.4 + const QgsRectangle* filterExtent = 0, + QgsWKBTypes::Type overrideGeometryType = QgsWKBTypes::Unknown, + bool forceMulti = false, + bool includeZ = false ); /** Create shapefile and initialize it */ diff --git a/src/ui/qgsvectorlayersaveasdialogbase.ui b/src/ui/qgsvectorlayersaveasdialogbase.ui index a81b6c6567c..270850d3643 100644 --- a/src/ui/qgsvectorlayersaveasdialogbase.ui +++ b/src/ui/qgsvectorlayersaveasdialogbase.ui @@ -91,7 +91,7 @@ 0 0 555 - 523 + 650 @@ -177,6 +177,43 @@ + + + + Geometry + + + + + + + + Geometry type + + + + + + + + + + + + Force multi-type + + + + + + + Include z-dimension + + + + + + @@ -320,15 +357,15 @@ - QgsCollapsibleGroupBox - QGroupBox -
qgscollapsiblegroupbox.h
+ QgsProjectionSelectionWidget + QWidget +
qgsprojectionselectionwidget.h
1
- QgsProjectionSelectionWidget - QWidget -
qgsprojectionselectionwidget.h
+ QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
1
@@ -350,6 +387,9 @@ mAddToCanvas mSymbologyExportComboBox mScaleSpinBox + mGeometryTypeComboBox + mForceMultiCheckBox + mIncludeZCheckBox mOgrDatasourceOptions mOgrLayerOptions buttonBox