[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.
This commit is contained in:
Nyall Dawson 2015-11-24 06:30:15 +11:00
parent 09e7102fda
commit ffebfd9bb7
7 changed files with 250 additions and 30 deletions

View File

@ -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 */

View File

@ -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 );
}

View File

@ -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:

View File

@ -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;

View File

@ -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 )

View File

@ -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 */

View File

@ -91,7 +91,7 @@
<x>0</x>
<y>0</y>
<width>555</width>
<height>523</height>
<height>650</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -177,6 +177,43 @@
</item>
</layout>
</item>
<item>
<widget class="QgsCollapsibleGroupBox" name="mGeometryGroupBox">
<property name="title">
<string>Geometry</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="mSymbologyExportLabel_2">
<property name="text">
<string>Geometry type</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="mGeometryTypeComboBox"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="mForceMultiCheckBox">
<property name="text">
<string>Force multi-type</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mIncludeZCheckBox">
<property name="text">
<string>Include z-dimension</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QgsExtentGroupBox" name="mExtentGroupBox"/>
</item>
@ -320,15 +357,15 @@
</widget>
<customwidgets>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends>
<header>qgsprojectionselectionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsProjectionSelectionWidget</class>
<extends>QWidget</extends>
<header location="global">qgsprojectionselectionwidget.h</header>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
@ -350,6 +387,9 @@
<tabstop>mAddToCanvas</tabstop>
<tabstop>mSymbologyExportComboBox</tabstop>
<tabstop>mScaleSpinBox</tabstop>
<tabstop>mGeometryTypeComboBox</tabstop>
<tabstop>mForceMultiCheckBox</tabstop>
<tabstop>mIncludeZCheckBox</tabstop>
<tabstop>mOgrDatasourceOptions</tabstop>
<tabstop>mOgrLayerOptions</tabstop>
<tabstop>buttonBox</tabstop>