Skip to content

Commit

Permalink
[FEATURE] Allow overide of geometry type in vector save as dialog
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nyalldawson committed Nov 23, 2015
1 parent 09e7102 commit ffebfd9
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 30 deletions.
37 changes: 33 additions & 4 deletions python/core/qgsvectorfilewriter.sip
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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 */
Expand Down
55 changes: 55 additions & 0 deletions src/app/ogr/qgsvectorlayersaveasdialog.cpp
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand All @@ -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 );
}
31 changes: 31 additions & 0 deletions src/app/ogr/qgsvectorlayersaveasdialog.h
Expand Up @@ -65,13 +65,44 @@ 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 );
void on_browseFilename_clicked();
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:
Expand Down
9 changes: 8 additions & 1 deletion src/app/qgisapp.cpp
Expand Up @@ -5476,13 +5476,16 @@ 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 )
{
QString encoding = dialog->encoding();
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 );
Expand Down Expand Up @@ -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;

Expand Down
56 changes: 42 additions & 14 deletions src/core/qgsvectorfilewriter.cpp
Expand Up @@ -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();
}
Expand All @@ -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 )
{
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 )
Expand All @@ -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;
}
Expand All @@ -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 )
{
Expand All @@ -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() )
Expand All @@ -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;
}
}
Expand All @@ -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 )
Expand Down
38 changes: 34 additions & 4 deletions src/core/qgsvectorfilewriter.h
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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 */
Expand Down

0 comments on commit ffebfd9

Please sign in to comment.