[FEATURE] Vector layer save as: offer file/layer overwriting, new layer creation, feature and field appending

When saving a vector layer into an existing file, depending on the capabilities
of the output driver, the user can now decide whether:
- to overwrite the whole file
- to overwrite only the target layer (layer name is now configurable)
- to append features to the existing target layer
- to append features, add new fields if there are any.

All above is available for drivers like GPKG, SpatiaLite, FileGDB, ...
For drivers like Shapefile, MapInfo .tab, feature append is also available.
This commit is contained in:
Even Rouault 2016-10-13 00:16:22 +02:00
parent a5ffc6a856
commit 34894c6f5a
8 changed files with 964 additions and 73 deletions

View File

@ -132,6 +132,43 @@ class QgsVectorFileWriter
virtual QVariant convert( int fieldIdxInLayer, const QVariant& value ); virtual QVariant convert( int fieldIdxInLayer, const QVariant& value );
}; };
/** Edition capability flags
* @note Added in QGIS 3.0 */
enum EditionCapability
{
/** Flag to indicate that a new layer can be added to the dataset */
CanAddNewLayer,
/** Flag to indicate that new features can be added to an existing layer */
CanAppendToExistingLayer ,
/** Flag to indicate that new fields can be added to an existing layer. Imply CanAppendToExistingLayer */
CanAddNewFieldsToExistingLayer,
/** Flag to indicate that an existing layer can be deleted */
CanDeleteLayer
};
typedef QFlags<QgsVectorFileWriter::EditionCapability> EditionCapabilities;
/** Enumeration to describe how to handle existing files
@note Added in QGIS 3.0
*/
enum ActionOnExistingFile
{
/** Create or overwrite file */
CreateOrOverwriteFile,
/** Create or overwrite layer */
CreateOrOverwriteLayer,
/** Append features to existing layer, but do not create new fields */
AppendToLayerNoNewFields,
/** Append features to existing layer, and create new fields if needed */
AppendToLayerAddFields
};
/** Write contents of vector layer to an (OGR supported) vector formt /** Write contents of vector layer to an (OGR supported) vector formt
* @param layer layer to write * @param layer layer to write
* @param fileName file name to write to * @param fileName file name to write to
@ -220,6 +257,88 @@ class QgsVectorFileWriter
FieldValueConverter* fieldValueConverter = nullptr FieldValueConverter* fieldValueConverter = nullptr
); );
/**
* Options to pass to writeAsVectorFormat()
* @note Added in QGIS 3.0
*/
class SaveVectorOptions
{
public:
/** Constructor */
SaveVectorOptions();
/** Destructor */
virtual ~SaveVectorOptions();
/** OGR driver to use */
QString driverName;
/** Layer name. If let empty, it will be derived from the filename */
QString layerName;
/** Action on existing file */
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile;
/** Encoding to use */
QString fileEncoding;
/** Transform to reproject exported geometries with, or invalid transform
* for no transformation */
QgsCoordinateTransform ct;
/** Write only selected features of layer */
bool onlySelectedFeatures;
/** List of OGR data source creation options */
QStringList datasourceOptions;
/** List of OGR layer creation options */
QStringList layerOptions;
/** Only write geometries */
bool skipAttributeCreation;
/** Attributes to export (empty means all unless skipAttributeCreation is set) */
QgsAttributeList attributes;
/** Symbology to export */
QgsVectorFileWriter::SymbologyExport symbologyExport;
/** Scale of symbology */
double symbologyScale;
/** If not empty, only features intersecting the extent will be saved */
QgsRectangle filterExtent;
/** 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 */
QgsWkbTypes::Type overrideGeometryType;
/** Set to true to force creation of multi* geometries */
bool forceMulti;
/** Set to true to include z dimension in output. This option is only valid if overrideGeometryType is set */
bool includeZ;
/** Field value converter */
QgsVectorFileWriter::FieldValueConverter* fieldValueConverter;
};
/** Writes a layer out to a vector file.
* @param layer source layer to write
* @param fileName file name to write to
* @param options options.
* @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
* @param errorMessage pointer to buffer fo error message
* @note added in 3.0
*/
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
const QString& fileName,
const SaveVectorOptions& options,
QString *newFilename = nullptr,
QString *errorMessage = nullptr );
/** Create a new vector file writer */ /** Create a new vector file writer */
QgsVectorFileWriter( const QString& vectorFileName, QgsVectorFileWriter( const QString& vectorFileName,
const QString& fileEncoding, const QString& fileEncoding,
@ -294,6 +413,27 @@ class QgsVectorFileWriter
*/ */
static QStringList defaultLayerOptions( const QString& driverName ); static QStringList defaultLayerOptions( const QString& driverName );
/**
* Return edition capabilites for an existing dataset name.
* @note added in QGIS 3.0
*/
static EditionCapabilities editionCapabilities( const QString& datasetName );
/**
* Returns whether the target layer already exists.
* @note added in QGIS 3.0
*/
static bool targetLayerExists( const QString& datasetName,
const QString& layerName );
/**
* Returns whether there are among the attributes specified some that do not exist yet in the layer
* @note added in QGIS 3.0
*/
static bool areThereNewFieldsToCreate( const QString& datasetName,
const QString& layerName,
QgsVectorLayer* layer,
const QgsAttributeList& attributes );
protected: protected:
//! @note not available in python bindings //! @note not available in python bindings
// OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType ); // OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType );
@ -302,3 +442,5 @@ class QgsVectorFileWriter
QgsVectorFileWriter( const QgsVectorFileWriter& rh ); QgsVectorFileWriter( const QgsVectorFileWriter& rh );
}; };
QFlags<QgsVectorFileWriter::EditionCapability> operator|(QgsVectorFileWriter::EditionCapability f1, QFlags<QgsVectorFileWriter::EditionCapability> f2);

View File

@ -23,6 +23,7 @@
#include "qgseditorwidgetfactory.h" #include "qgseditorwidgetfactory.h"
#include "qgseditorwidgetregistry.h" #include "qgseditorwidgetregistry.h"
#include <QMessageBox>
#include <QSettings> #include <QSettings>
#include <QFileDialog> #include <QFileDialog>
#include <QTextCodec> #include <QTextCodec>
@ -37,6 +38,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( long srsid, QWidget* par
, mLayer( 0 ) , mLayer( 0 )
, mAttributeTableItemChangedSlotEnabled( true ) , mAttributeTableItemChangedSlotEnabled( true )
, mReplaceRawFieldValuesStateChangedSlotEnabled( true ) , mReplaceRawFieldValuesStateChangedSlotEnabled( true )
, mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
{ {
setup(); setup();
} }
@ -46,6 +48,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( QgsVectorLayer *layer, i
, mLayer( layer ) , mLayer( layer )
, mAttributeTableItemChangedSlotEnabled( true ) , mAttributeTableItemChangedSlotEnabled( true )
, mReplaceRawFieldValuesStateChangedSlotEnabled( true ) , mReplaceRawFieldValuesStateChangedSlotEnabled( true )
, mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
{ {
if ( layer ) if ( layer )
{ {
@ -210,6 +213,121 @@ QgsVectorLayerSaveAsDialog::~QgsVectorLayerSaveAsDialog()
void QgsVectorLayerSaveAsDialog::accept() void QgsVectorLayerSaveAsDialog::accept()
{ {
if ( QFile::exists( filename() ) )
{
QgsVectorFileWriter::EditionCapabilities caps =
QgsVectorFileWriter::editionCapabilities( filename() );
bool layerExists = QgsVectorFileWriter::targetLayerExists( filename(),
layername() );
if ( layerExists )
{
if ( !( caps & QgsVectorFileWriter::CanAppendToExistingLayer ) &&
( caps & QgsVectorFileWriter::CanDeleteLayer ) &&
( caps & QgsVectorFileWriter::CanAddNewLayer ) )
{
QMessageBox msgBox;
msgBox.setIcon( QMessageBox::Question );
msgBox.setWindowTitle( tr( "The layer already exists" ) );
msgBox.setText( tr( "Do you want to overwrite the whole file or overwrite the layer?" ) );
QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole );
msgBox.setStandardButtons( QMessageBox::Cancel );
msgBox.setDefaultButton( QMessageBox::Cancel );
int ret = msgBox.exec();
if ( ret == QMessageBox::Cancel )
return;
if ( msgBox.clickedButton() == overwriteFileButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
else if ( msgBox.clickedButton() == overwriteLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
else if ( !( caps & QgsVectorFileWriter::CanAppendToExistingLayer ) )
{
if ( QMessageBox::question( this,
tr( "The file already exists" ),
tr( "Do you want to overwrite the existing file?" ) ) == QMessageBox::NoButton )
{
return;
}
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
}
else if (( caps & QgsVectorFileWriter::CanDeleteLayer ) &&
( caps & QgsVectorFileWriter::CanAddNewLayer ) )
{
QMessageBox msgBox;
msgBox.setIcon( QMessageBox::Question );
msgBox.setWindowTitle( tr( "The layer already exists" ) );
msgBox.setText( tr( "Do you want to overwrite the whole file, overwrite the layer or append features to the layer?" ) );
QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole );
QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole );
msgBox.setStandardButtons( QMessageBox::Cancel );
msgBox.setDefaultButton( QMessageBox::Cancel );
int ret = msgBox.exec();
if ( ret == QMessageBox::Cancel )
return;
if ( msgBox.clickedButton() == overwriteFileButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
else if ( msgBox.clickedButton() == overwriteLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
else if ( msgBox.clickedButton() == appendToLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
}
else
{
QMessageBox msgBox;
msgBox.setIcon( QMessageBox::Question );
msgBox.setWindowTitle( tr( "The layer already exists" ) );
msgBox.setText( tr( "Do you want to overwrite the whole file or append features to the layer?" ) );
QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole );
msgBox.setStandardButtons( QMessageBox::Cancel );
msgBox.setDefaultButton( QMessageBox::Cancel );
int ret = msgBox.exec();
if ( ret == QMessageBox::Cancel )
return;
if ( msgBox.clickedButton() == overwriteFileButton )
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
else if ( msgBox.clickedButton() == appendToLayerButton )
mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
}
if ( mActionOnExistingFile == QgsVectorFileWriter::AppendToLayerNoNewFields )
{
if ( QgsVectorFileWriter::areThereNewFieldsToCreate( filename(),
layername(),
mLayer,
selectedAttributes() ) )
{
if ( QMessageBox::question( this,
tr( "The existing layer has different fields" ),
tr( "Do you want to add the missing fields to the layer?" ) ) == QMessageBox::Yes )
{
mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerAddFields;
}
}
}
}
else
{
if (( caps & QgsVectorFileWriter::CanAddNewLayer ) )
{
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
else
{
if ( QMessageBox::question( this,
tr( "The file already exists" ),
tr( "Do you want to overwrite the existing file?" ) ) == QMessageBox::NoButton )
{
return;
}
mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
}
}
}
QSettings settings; QSettings settings;
settings.setValue( "/UI/lastVectorFileFilterDir", QFileInfo( filename() ).absolutePath() ); settings.setValue( "/UI/lastVectorFileFilterDir", QFileInfo( filename() ).absolutePath() );
settings.setValue( "/UI/lastVectorFormat", format() ); settings.setValue( "/UI/lastVectorFormat", format() );
@ -226,12 +344,13 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
bool selectAllFields = true; bool selectAllFields = true;
bool fieldsAsDisplayedValues = false; bool fieldsAsDisplayedValues = false;
if ( format() == "KML" ) const QString sFormat( format() );
if ( sFormat == "KML" )
{ {
mAttributesSelection->setEnabled( true ); mAttributesSelection->setEnabled( true );
selectAllFields = false; selectAllFields = false;
} }
else if ( format() == "DXF" ) else if ( sFormat == "DXF" )
{ {
mAttributesSelection->setEnabled( false ); mAttributesSelection->setEnabled( false );
selectAllFields = false; selectAllFields = false;
@ -239,7 +358,23 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
else else
{ {
mAttributesSelection->setEnabled( true ); mAttributesSelection->setEnabled( true );
fieldsAsDisplayedValues = ( format() == "CSV" || format() == "XLS" || format() == "XLSX" || format() == "ODS" ); fieldsAsDisplayedValues = ( sFormat == "CSV" || sFormat == "XLS" || sFormat == "XLSX" || sFormat == "ODS" );
}
leLayername->setEnabled( sFormat == "KML" ||
sFormat == "GPKG" ||
sFormat == "XLSX" ||
sFormat == "ODS" ||
sFormat == "FileGDB" ||
sFormat == "SQLite" ||
sFormat == "SpatiaLite" );
if ( !leLayername->isEnabled() )
leLayername->setText( QString() );
else if ( leLayername->text().isEmpty() &&
!leFilename->text().isEmpty() )
{
QString layerName = QFileInfo( leFilename->text() ).baseName();
leLayername->setText( layerName ) ;
} }
if ( mLayer ) if ( mLayer )
@ -485,7 +620,14 @@ void QgsVectorLayerSaveAsDialog::on_mAttributeTable_itemChanged( QTableWidgetIte
void QgsVectorLayerSaveAsDialog::on_leFilename_textChanged( const QString& text ) void QgsVectorLayerSaveAsDialog::on_leFilename_textChanged( const QString& text )
{ {
buttonBox->button( QDialogButtonBox::Ok )->setEnabled( QFileInfo( text ).absoluteDir().exists() ); buttonBox->button( QDialogButtonBox::Ok )->setEnabled(
!text.isEmpty() && QFileInfo( text ).absoluteDir().exists() );
if ( leLayername->isEnabled() )
{
QString layerName = QFileInfo( text ).baseName();
leLayername->setText( layerName );
}
} }
void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked() void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
@ -493,7 +635,7 @@ void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
QSettings settings; QSettings settings;
QString dirName = leFilename->text().isEmpty() ? settings.value( "/UI/lastVectorFileFilterDir", QDir::homePath() ).toString() : leFilename->text(); QString dirName = leFilename->text().isEmpty() ? settings.value( "/UI/lastVectorFileFilterDir", QDir::homePath() ).toString() : leFilename->text();
QString filterString = QgsVectorFileWriter::filterForDriver( format() ); QString filterString = QgsVectorFileWriter::filterForDriver( format() );
QString outputFile = QFileDialog::getSaveFileName( nullptr, tr( "Save layer as..." ), dirName, filterString ); QString outputFile = QFileDialog::getSaveFileName( nullptr, tr( "Save layer as..." ), dirName, filterString, nullptr, QFileDialog::DontConfirmOverwrite );
if ( !outputFile.isNull() ) if ( !outputFile.isNull() )
{ {
leFilename->setText( outputFile ); leFilename->setText( outputFile );
@ -511,6 +653,11 @@ QString QgsVectorLayerSaveAsDialog::filename() const
return leFilename->text(); return leFilename->text();
} }
QString QgsVectorLayerSaveAsDialog::layername() const
{
return leLayername->text();
}
QString QgsVectorLayerSaveAsDialog::encoding() const QString QgsVectorLayerSaveAsDialog::encoding() const
{ {
return mEncodingComboBox->currentText(); return mEncodingComboBox->currentText();
@ -611,7 +758,7 @@ QStringList QgsVectorLayerSaveAsDialog::layerOptions() const
case QgsVectorFileWriter::String: case QgsVectorFileWriter::String:
{ {
QLineEdit* le = mLayerOptionsGroupBox->findChild<QLineEdit*>( it.key() ); QLineEdit* le = mLayerOptionsGroupBox->findChild<QLineEdit*>( it.key() );
if ( le ) if ( le && !le->text().isEmpty() )
options << QString( "%1=%2" ).arg( it.key(), le->text() ); options << QString( "%1=%2" ).arg( it.key(), le->text() );
break; break;
} }
@ -730,6 +877,11 @@ bool QgsVectorLayerSaveAsDialog::includeZ() const
return mIncludeZCheckBox->isChecked(); return mIncludeZCheckBox->isChecked();
} }
QgsVectorFileWriter::ActionOnExistingFile QgsVectorLayerSaveAsDialog::creationActionOnExistingFile() const
{
return mActionOnExistingFile;
}
void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked ) void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked )
{ {
mIncludeZCheckBox->setChecked( checked ); mIncludeZCheckBox->setChecked( checked );

View File

@ -48,6 +48,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
QString format() const; QString format() const;
QString encoding() const; QString encoding() const;
QString filename() const; QString filename() const;
QString layername() const;
QStringList datasourceOptions() const; QStringList datasourceOptions() const;
QStringList layerOptions() const; QStringList layerOptions() const;
long crs() const; long crs() const;
@ -100,6 +101,9 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
*/ */
void setIncludeZ( bool checked ); void setIncludeZ( bool checked );
/** Returns creation action */
QgsVectorFileWriter::ActionOnExistingFile creationActionOnExistingFile() const;
private slots: private slots:
void on_mFormatComboBox_currentIndexChanged( int idx ); void on_mFormatComboBox_currentIndexChanged( int idx );
@ -126,6 +130,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
QgsVectorLayer *mLayer; QgsVectorLayer *mLayer;
bool mAttributeTableItemChangedSlotEnabled; bool mAttributeTableItemChangedSlotEnabled;
bool mReplaceRawFieldValuesStateChangedSlotEnabled; bool mReplaceRawFieldValuesStateChangedSlotEnabled;
QgsVectorFileWriter::ActionOnExistingFile mActionOnExistingFile;
}; };
#endif // QGSVECTORLAYERSAVEASDIALOG_H #endif // QGSVECTORLAYERSAVEASDIALOG_H

View File

@ -3521,7 +3521,11 @@ bool QgisApp::addVectorLayers( const QStringList &theLayerQStringList, const QSt
QString base; QString base;
if ( dataSourceType == "file" ) if ( dataSourceType == "file" )
{ {
QFileInfo fi( src ); QString srcWithoutLayername( src );
int posPipe = srcWithoutLayername.indexOf( '|' );
if ( posPipe >= 0 )
srcWithoutLayername.resize( posPipe );
QFileInfo fi( srcWithoutLayername );
base = fi.completeBaseName(); base = fi.completeBaseName();
// if needed prompt for zipitem layers // if needed prompt for zipitem layers
@ -6228,22 +6232,29 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
// No need to use the converter if there is nothing to convert // No need to use the converter if there is nothing to convert
if ( !dialog->attributesAsDisplayedValues().isEmpty() ) if ( !dialog->attributesAsDisplayedValues().isEmpty() )
converterPtr = &converter; converterPtr = &converter;
QgsVectorFileWriter::SaveVectorOptions options;
options.driverName = format;
options.layerName = dialog->layername();
options.actionOnExistingFile = dialog->creationActionOnExistingFile();
options.fileEncoding = encoding;
options.ct = ct;
options.onlySelectedFeatures = dialog->onlySelected();
options.datasourceOptions = datasourceOptions;
options.layerOptions = dialog->layerOptions();
options.skipAttributeCreation = dialog->selectedAttributes().isEmpty();
options.symbologyExport = static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() );
options.symbologyScale = dialog->scaleDenominator();
if ( dialog->hasFilterExtent() )
options.filterExtent = filterExtent;
options.overrideGeometryType = autoGeometryType ? QgsWkbTypes::Unknown : forcedGeometryType;
options.forceMulti = dialog->forceMulti();
options.includeZ = dialog->includeZ();
options.attributes = dialog->selectedAttributes();
options.fieldValueConverter = converterPtr;
error = QgsVectorFileWriter::writeAsVectorFormat( error = QgsVectorFileWriter::writeAsVectorFormat(
vlayer, vectorFilename, encoding, ct, format, vlayer, vectorFilename, options, &newFilename, &errorMessage );
dialog->onlySelected(),
&errorMessage,
datasourceOptions, dialog->layerOptions(),
dialog->selectedAttributes().isEmpty(),
&newFilename,
static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() ),
dialog->scaleDenominator(),
dialog->hasFilterExtent() ? &filterExtent : nullptr,
autoGeometryType ? QgsWkbTypes::Unknown : forcedGeometryType,
dialog->forceMulti(),
dialog->includeZ(),
dialog->selectedAttributes(),
converterPtr
);
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
@ -6251,7 +6262,10 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
{ {
if ( dialog->addToCanvas() ) if ( dialog->addToCanvas() )
{ {
addVectorLayers( QStringList( newFilename ), encoding, "file" ); QString uri( newFilename );
if ( !dialog->layername().isEmpty() )
uri += "|layername=" + dialog->layername();
addVectorLayers( QStringList( uri ), encoding, "file" );
} }
emit layerSavedAs( vlayer, vectorFilename ); emit layerSavedAs( vlayer, vectorFilename );
messageBar()->pushMessage( tr( "Saving done" ), messageBar()->pushMessage( tr( "Saving done" ),

View File

@ -98,7 +98,8 @@ QgsVectorFileWriter::QgsVectorFileWriter(
, mFieldValueConverter( nullptr ) , mFieldValueConverter( nullptr )
{ {
init( theVectorFileName, theFileEncoding, fields, geometryType , init( theVectorFileName, theFileEncoding, fields, geometryType ,
srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr ); srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr,
QString(), CreateOrOverwriteFile );
} }
QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName, QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
@ -111,7 +112,9 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
const QStringList& layerOptions, const QStringList& layerOptions,
QString* newFilename, QString* newFilename,
QgsVectorFileWriter::SymbologyExport symbologyExport, QgsVectorFileWriter::SymbologyExport symbologyExport,
FieldValueConverter* fieldValueConverter ) FieldValueConverter* fieldValueConverter,
const QString& layerName,
ActionOnExistingFile action )
: mDS( nullptr ) : mDS( nullptr )
, mLayer( nullptr ) , mLayer( nullptr )
, mOgrRef( nullptr ) , mOgrRef( nullptr )
@ -124,7 +127,8 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
, mFieldValueConverter( nullptr ) , mFieldValueConverter( nullptr )
{ {
init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName, init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName,
datasourceOptions, layerOptions, newFilename, fieldValueConverter ); datasourceOptions, layerOptions, newFilename, fieldValueConverter,
layerName, action );
} }
void QgsVectorFileWriter::init( QString vectorFileName, void QgsVectorFileWriter::init( QString vectorFileName,
@ -136,7 +140,9 @@ void QgsVectorFileWriter::init( QString vectorFileName,
QStringList datasourceOptions, QStringList datasourceOptions,
QStringList layerOptions, QStringList layerOptions,
QString* newFilename, QString* newFilename,
FieldValueConverter* fieldValueConverter ) FieldValueConverter* fieldValueConverter,
const QString& layerNameIn,
ActionOnExistingFile action )
{ {
mRenderContext.setRendererScale( mSymbologyScaleDenominator ); mRenderContext.setRendererScale( mSymbologyScaleDenominator );
@ -224,6 +230,7 @@ void QgsVectorFileWriter::init( QString vectorFileName,
} }
#endif #endif
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
deleteShapeFile( vectorFileName ); deleteShapeFile( vectorFileName );
} }
else else
@ -247,8 +254,28 @@ void QgsVectorFileWriter::init( QString vectorFileName,
} }
} }
if ( action == CreateOrOverwriteFile )
{
if ( vectorFileName.endsWith( ".gdb", Qt::CaseInsensitive ) )
{
QDir dir( vectorFileName );
if ( dir.exists() )
{
QFileInfoList fileList = dir.entryInfoList(
QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst );
Q_FOREACH ( QFileInfo info, fileList )
{
QFile::remove( info.absoluteFilePath() );
}
}
QDir().rmdir( vectorFileName );
}
else
{
QFile::remove( vectorFileName ); QFile::remove( vectorFileName );
} }
}
}
if ( metadataFound && !metadata.compulsoryEncoding.isEmpty() ) if ( metadataFound && !metadata.compulsoryEncoding.isEmpty() )
{ {
@ -272,7 +299,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
} }
// create the data source // create the data source
if ( action == CreateOrOverwriteFile )
mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options ); mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
else
mDS = OGROpen( TO8F( vectorFileName ), TRUE, nullptr );
if ( options ) if ( options )
{ {
@ -285,12 +315,43 @@ void QgsVectorFileWriter::init( QString vectorFileName,
if ( !mDS ) if ( !mDS )
{ {
mError = ErrCreateDataSource; mError = ErrCreateDataSource;
if ( action == CreateOrOverwriteFile )
mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" ) mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ); .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
else
mErrorMessage = QObject::tr( "opening of data source in update mode failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
return; return;
} }
QString layerName( layerNameIn );
if ( layerName.isEmpty() )
layerName = QFileInfo( vectorFileName ).baseName();
if ( action == CreateOrOverwriteLayer )
{
const int layer_count = OGR_DS_GetLayerCount( mDS );
for ( int i = 0; i < layer_count; i++ )
{
OGRLayerH hLayer = OGR_DS_GetLayer( mDS, i );
if ( EQUAL( OGR_L_GetName( hLayer ), TO8F( layerName ) ) )
{
if ( OGR_DS_DeleteLayer( mDS, i ) != OGRERR_NONE )
{
mError = ErrCreateLayer;
mErrorMessage = QObject::tr( "overwriting of existing layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
return;
}
break;
}
}
}
if ( action == CreateOrOverwriteFile )
QgsDebugMsg( "Created data source" ); QgsDebugMsg( "Created data source" );
else
QgsDebugMsg( "Opened data source in update mode" );
// use appropriate codec // use appropriate codec
mCodec = QTextCodec::codecForName( fileEncoding.toLocal8Bit().constData() ); mCodec = QTextCodec::codecForName( fileEncoding.toLocal8Bit().constData() );
@ -318,7 +379,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
} }
// datasource created, now create the output layer // datasource created, now create the output layer
QString layerName = QFileInfo( vectorFileName ).baseName();
OGRwkbGeometryType wkbType = ogrTypeFromWkbType( geometryType ); OGRwkbGeometryType wkbType = ogrTypeFromWkbType( geometryType );
// Remove FEATURE_DATASET layer option (used for ESRI File GDB driver) if its value is not set // Remove FEATURE_DATASET layer option (used for ESRI File GDB driver) if its value is not set
@ -341,7 +401,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
// disable encoding conversion of OGR Shapefile layer // disable encoding conversion of OGR Shapefile layer
CPLSetConfigOption( "SHAPE_ENCODING", "" ); CPLSetConfigOption( "SHAPE_ENCODING", "" );
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options ); mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
else
mLayer = OGR_DS_GetLayerByName( mDS, TO8F( layerName ) );
if ( options ) if ( options )
{ {
@ -378,8 +441,12 @@ void QgsVectorFileWriter::init( QString vectorFileName,
if ( !mLayer ) if ( !mLayer )
{ {
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" ) mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ); .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
else
mErrorMessage = QObject::tr( "opening of layer failed (OGR error:%1)" )
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
mError = ErrCreateLayer; mError = ErrCreateLayer;
return; return;
} }
@ -397,17 +464,30 @@ void QgsVectorFileWriter::init( QString vectorFileName,
mFieldValueConverter = fieldValueConverter; mFieldValueConverter = fieldValueConverter;
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx ) for ( int fldIdx = 0; ( action == CreateOrOverwriteFile ||
action == CreateOrOverwriteLayer ||
action == AppendToLayerAddFields ) &&
fldIdx < fields.count(); ++fldIdx )
{ {
QgsField attrField = fields.at( fldIdx ); QgsField attrField = fields.at( fldIdx );
OGRFieldType ogrType = OFTString; //default to string
if ( fieldValueConverter ) if ( fieldValueConverter )
{ {
attrField = fieldValueConverter->fieldDefinition( fields.at( fldIdx ) ); attrField = fieldValueConverter->fieldDefinition( fields.at( fldIdx ) );
} }
QString name( attrField.name() );
if ( action == AppendToLayerAddFields )
{
int ogrIdx = OGR_FD_GetFieldIndex( defn, mCodec->fromUnicode( name ) );
if ( ogrIdx >= 0 )
{
mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
continue;
}
}
OGRFieldType ogrType = OFTString; //default to string
int ogrWidth = attrField.length(); int ogrWidth = attrField.length();
int ogrPrecision = attrField.precision(); int ogrPrecision = attrField.precision();
if ( ogrPrecision > 0 ) if ( ogrPrecision > 0 )
@ -486,8 +566,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
return; return;
} }
QString name( attrField.name() );
if ( mOgrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 ) if ( mOgrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 )
{ {
int i; int i;
@ -581,6 +659,18 @@ void QgsVectorFileWriter::init( QString vectorFileName,
mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx ); mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
} }
if ( action == AppendToLayerNoNewFields )
{
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
{
QgsField attrField = fields.at( fldIdx );
QString name( attrField.name() );
int ogrIdx = OGR_FD_GetFieldIndex( defn, mCodec->fromUnicode( name ) );
if ( ogrIdx >= 0 )
mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
}
}
QgsDebugMsg( "Done creating fields" ); QgsDebugMsg( "Done creating fields" );
mWkbType = geometryType; mWkbType = geometryType;
@ -2072,6 +2162,7 @@ void QgsVectorFileWriter::resetMap( const QgsAttributeList &attributes )
mAttrIdxToOgrIdx.clear(); mAttrIdxToOgrIdx.clear();
for ( int i = 0; i < attributes.size(); i++ ) for ( int i = 0; i < attributes.size(); i++ )
{ {
if ( omap.find( i ) != omap.end() )
mAttrIdxToOgrIdx.insert( attributes[i], omap[i] ); mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
} }
} }
@ -2161,6 +2252,57 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
bool includeZ, bool includeZ,
QgsAttributeList attributes, QgsAttributeList attributes,
FieldValueConverter* fieldValueConverter ) FieldValueConverter* fieldValueConverter )
{
SaveVectorOptions options;
options.fileEncoding = fileEncoding;
options.ct = ct;
options.driverName = driverName;
options.onlySelectedFeatures = onlySelected;
options.datasourceOptions = datasourceOptions;
options.layerOptions = layerOptions;
options.skipAttributeCreation = skipAttributeCreation;
options.symbologyExport = symbologyExport;
options.symbologyScale = symbologyScale;
if ( filterExtent )
options.filterExtent = *filterExtent;
options.overrideGeometryType = overrideGeometryType;
options.forceMulti = forceMulti;
options.includeZ = includeZ;
options.attributes = attributes;
options.fieldValueConverter = fieldValueConverter;
return writeAsVectorFormat( layer, fileName, options, newFilename, errorMessage );
}
QgsVectorFileWriter::SaveVectorOptions::SaveVectorOptions()
: driverName( "ESRI Shapefile" )
, layerName( QString() )
, actionOnExistingFile( CreateOrOverwriteFile )
, fileEncoding( QString() )
, ct( QgsCoordinateTransform() )
, onlySelectedFeatures( false )
, datasourceOptions( QStringList() )
, layerOptions( QStringList() )
, skipAttributeCreation( false )
, attributes( QgsAttributeList() )
, symbologyExport( NoSymbology )
, symbologyScale( 1.0 )
, filterExtent( QgsRectangle() )
, overrideGeometryType( QgsWkbTypes::Unknown )
, forceMulti( false )
, fieldValueConverter( nullptr )
{
}
QgsVectorFileWriter::SaveVectorOptions::~SaveVectorOptions()
{
}
QgsVectorFileWriter::WriterError
QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer,
const QString& fileName,
const SaveVectorOptions& options,
QString *newFilename,
QString *errorMessage )
{ {
if ( !layer ) if ( !layer )
{ {
@ -2169,10 +2311,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
bool shallTransform = false; bool shallTransform = false;
QgsCoordinateReferenceSystem outputCRS; QgsCoordinateReferenceSystem outputCRS;
if ( ct.isValid() ) if ( options.ct.isValid() )
{ {
// This means we should transform // This means we should transform
outputCRS = ct.destinationCrs(); outputCRS = options.ct.destinationCrs();
shallTransform = true; shallTransform = true;
} }
else else
@ -2182,18 +2324,19 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
} }
QgsWkbTypes::Type destWkbType = layer->wkbType(); QgsWkbTypes::Type destWkbType = layer->wkbType();
if ( overrideGeometryType != QgsWkbTypes::Unknown ) if ( options.overrideGeometryType != QgsWkbTypes::Unknown )
{ {
destWkbType = QgsWkbTypes::flatType( overrideGeometryType ); destWkbType = QgsWkbTypes::flatType( options.overrideGeometryType );
if ( QgsWkbTypes::hasZ( overrideGeometryType ) || includeZ ) if ( QgsWkbTypes::hasZ( options.overrideGeometryType ) || options.includeZ )
destWkbType = QgsWkbTypes::addZ( destWkbType ); destWkbType = QgsWkbTypes::addZ( destWkbType );
} }
if ( forceMulti ) if ( options.forceMulti )
{ {
destWkbType = QgsWkbTypes::multiType( destWkbType ); destWkbType = QgsWkbTypes::multiType( destWkbType );
} }
if ( skipAttributeCreation ) QgsAttributeList attributes( options.attributes );
if ( options.skipAttributeCreation )
attributes.clear(); attributes.clear();
else if ( attributes.isEmpty() ) else if ( attributes.isEmpty() )
{ {
@ -2231,7 +2374,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
if ( layer->storageType() == "ESRI Shapefile" && !QgsWkbTypes::isMultiType( destWkbType ) ) if ( layer->storageType() == "ESRI Shapefile" && !QgsWkbTypes::isMultiType( destWkbType ) )
{ {
QgsFeatureRequest req; QgsFeatureRequest req;
if ( onlySelected ) if ( options.onlySelectedFeatures )
{ {
req.setFilterFids( layer->selectedFeaturesIds() ); req.setFilterFids( layer->selectedFeaturesIds() );
} }
@ -2265,11 +2408,17 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
} }
QgsVectorFileWriter* writer = QgsVectorFileWriter* writer =
new QgsVectorFileWriter( fileName, fileEncoding, fields, destWkbType, new QgsVectorFileWriter( fileName,
outputCRS, driverName, datasourceOptions, layerOptions, options.fileEncoding, fields, destWkbType,
newFilename, symbologyExport, outputCRS, options.driverName,
fieldValueConverter ); options.datasourceOptions,
writer->setSymbologyScaleDenominator( symbologyScale ); options.layerOptions,
newFilename,
options.symbologyExport,
options.fieldValueConverter,
options.layerName,
options.actionOnExistingFile );
writer->setSymbologyScaleDenominator( options.symbologyScale );
if ( newFilename ) if ( newFilename )
{ {
@ -2302,7 +2451,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
req.setFlags( QgsFeatureRequest::NoGeometry ); req.setFlags( QgsFeatureRequest::NoGeometry );
} }
req.setSubsetOfAttributes( attributes ); req.setSubsetOfAttributes( attributes );
if ( onlySelected ) if ( options.onlySelectedFeatures )
req.setFilterFids( layer->selectedFeaturesIds() ); req.setFilterFids( layer->selectedFeaturesIds() );
QgsFeatureIterator fit = layer->getFeatures( req ); QgsFeatureIterator fit = layer->getFeatures( req );
@ -2318,7 +2467,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
if ( r->capabilities() & QgsFeatureRenderer::SymbolLevels if ( r->capabilities() & QgsFeatureRenderer::SymbolLevels
&& r->usingSymbolLevels() ) && r->usingSymbolLevels() )
{ {
QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, ct, errorMessage ); QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, options.ct, errorMessage );
delete writer; delete writer;
return ( error == NoError ) ? NoError : ErrFeatureWriteFailed; return ( error == NoError ) ? NoError : ErrFeatureWriteFailed;
} }
@ -2328,9 +2477,9 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
//unit type //unit type
QgsUnitTypes::DistanceUnit mapUnits = layer->crs().mapUnits(); QgsUnitTypes::DistanceUnit mapUnits = layer->crs().mapUnits();
if ( ct.isValid() ) if ( options.ct.isValid() )
{ {
mapUnits = ct.destinationCrs().mapUnits(); mapUnits = options.ct.destinationCrs().mapUnits();
} }
writer->startRender( layer ); writer->startRender( layer );
@ -2358,7 +2507,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
if ( fet.hasGeometry() ) if ( fet.hasGeometry() )
{ {
QgsGeometry g = fet.geometry(); QgsGeometry g = fet.geometry();
g.transform( ct ); g.transform( options.ct );
fet.setGeometry( g ); fet.setGeometry( g );
} }
} }
@ -2376,10 +2525,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
} }
} }
if ( fet.hasGeometry() && filterExtent && !fet.geometry().intersects( *filterExtent ) ) if ( fet.hasGeometry() && !options.filterExtent.isNull() && !fet.geometry().intersects( options.filterExtent ) )
continue; continue;
if ( attributes.size() < 1 && skipAttributeCreation ) if ( attributes.size() < 1 && options.skipAttributeCreation )
{ {
fet.initAttributes( 0 ); fet.initAttributes( 0 );
} }
@ -2927,3 +3076,91 @@ QStringList QgsVectorFileWriter::concatenateOptions( const QMap<QString, QgsVect
return list; return list;
} }
QgsVectorFileWriter::EditionCapabilities QgsVectorFileWriter::editionCapabilities( const QString& datasetName )
{
OGRSFDriverH hDriver = nullptr;
OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
if ( !hDS )
return 0;
QString drvName = OGR_Dr_GetName( hDriver );
QgsVectorFileWriter::EditionCapabilities caps = 0;
if ( OGR_DS_TestCapability( hDS, ODsCCreateLayer ) )
{
// Shapefile driver returns True for a "foo.shp" dataset name,
// creating "bar.shp" new layer, but this would be a bit confusing
// for the user, so pretent that it does not support that
if ( !( drvName == "ESRI Shapefile" && QFile::exists( datasetName ) ) )
caps |= CanAddNewLayer;
}
if ( OGR_DS_TestCapability( hDS, ODsCDeleteLayer ) )
{
caps |= CanDeleteLayer;
}
int layer_count = OGR_DS_GetLayerCount( hDS );
if ( layer_count )
{
OGRLayerH hLayer = OGR_DS_GetLayer( hDS, 0 );
if ( hLayer )
{
if ( OGR_L_TestCapability( hLayer, OLCSequentialWrite ) )
{
caps |= CanAppendToExistingLayer;
if ( OGR_L_TestCapability( hLayer, OLCCreateField ) )
{
caps |= CanAddNewFieldsToExistingLayer;
}
}
}
}
OGR_DS_Destroy( hDS );
return caps;
}
bool QgsVectorFileWriter::targetLayerExists( const QString& datasetName,
const QString& layerNameIn )
{
OGRSFDriverH hDriver = nullptr;
OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
if ( !hDS )
return false;
QString layerName( layerNameIn );
if ( layerName.isEmpty() )
layerName = QFileInfo( datasetName ).baseName();
bool ret = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
OGR_DS_Destroy( hDS );
return ret;
}
bool QgsVectorFileWriter::areThereNewFieldsToCreate( const QString& datasetName,
const QString& layerName,
QgsVectorLayer* layer,
const QgsAttributeList& attributes )
{
OGRSFDriverH hDriver = nullptr;
OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
if ( !hDS )
return false;
OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
if ( !hLayer )
{
OGR_DS_Destroy( hDS );
return false;
}
bool ret = false;
OGRFeatureDefnH defn = OGR_L_GetLayerDefn( hLayer );
Q_FOREACH ( int idx, attributes )
{
QgsField fld = layer->fields().at( idx );
if ( OGR_FD_GetFieldIndex( defn, TO8F( fld.name() ) ) < 0 )
{
ret = true;
break;
}
}
OGR_DS_Destroy( hDS );
return ret;
}

View File

@ -199,6 +199,45 @@ class CORE_EXPORT QgsVectorFileWriter
virtual QVariant convert( int fieldIdxInLayer, const QVariant& value ); virtual QVariant convert( int fieldIdxInLayer, const QVariant& value );
}; };
/** Edition capability flags
* @note Added in QGIS 3.0 */
enum EditionCapability
{
/** Flag to indicate that a new layer can be added to the dataset */
CanAddNewLayer = 1 << 0,
/** Flag to indicate that new features can be added to an existing layer */
CanAppendToExistingLayer = 1 << 1,
/** Flag to indicate that new fields can be added to an existing layer. Imply CanAppendToExistingLayer */
CanAddNewFieldsToExistingLayer = 1 << 2,
/** Flag to indicate that an existing layer can be deleted */
CanDeleteLayer = 1 << 3
};
/** Combination of CanAddNewLayer, CanAppendToExistingLayer, CanAddNewFieldsToExistingLayer or CanDeleteLayer
* @note Added in QGIS 3.0 */
Q_DECLARE_FLAGS( EditionCapabilities, EditionCapability )
/** Enumeration to describe how to handle existing files
@note Added in QGIS 3.0
*/
typedef enum
{
/** Create or overwrite file */
CreateOrOverwriteFile,
/** Create or overwrite layer */
CreateOrOverwriteLayer,
/** Append features to existing layer, but do not create new fields */
AppendToLayerNoNewFields,
/** Append features to existing layer, and create new fields if needed */
AppendToLayerAddFields
} ActionOnExistingFile;
/** Write contents of vector layer to an (OGR supported) vector formt /** Write contents of vector layer to an (OGR supported) vector formt
* @param layer layer to write * @param layer layer to write
* @param fileName file name to write to * @param fileName file name to write to
@ -287,6 +326,88 @@ class CORE_EXPORT QgsVectorFileWriter
FieldValueConverter* fieldValueConverter = nullptr FieldValueConverter* fieldValueConverter = nullptr
); );
/** \ingroup core
* Options to pass to writeAsVectorFormat()
* @note Added in QGIS 3.0
*/
class CORE_EXPORT SaveVectorOptions
{
public:
/** Constructor */
SaveVectorOptions();
/** Destructor */
virtual ~SaveVectorOptions();
/** OGR driver to use */
QString driverName;
/** Layer name. If let empty, it will be derived from the filename */
QString layerName;
/** Action on existing file */
ActionOnExistingFile actionOnExistingFile;
/** Encoding to use */
QString fileEncoding;
/** Transform to reproject exported geometries with, or invalid transform
* for no transformation */
QgsCoordinateTransform ct;
/** Write only selected features of layer */
bool onlySelectedFeatures;
/** List of OGR data source creation options */
QStringList datasourceOptions;
/** List of OGR layer creation options */
QStringList layerOptions;
/** Only write geometries */
bool skipAttributeCreation;
/** Attributes to export (empty means all unless skipAttributeCreation is set) */
QgsAttributeList attributes;
/** Symbology to export */
SymbologyExport symbologyExport;
/** Scale of symbology */
double symbologyScale;
/** If not empty, only features intersecting the extent will be saved */
QgsRectangle filterExtent;
/** 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 */
QgsWkbTypes::Type overrideGeometryType;
/** Set to true to force creation of multi* geometries */
bool forceMulti;
/** Set to true to include z dimension in output. This option is only valid if overrideGeometryType is set */
bool includeZ;
/** Field value converter */
FieldValueConverter* fieldValueConverter;
};
/** Writes a layer out to a vector file.
* @param layer source layer to write
* @param fileName file name to write to
* @param options options.
* @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
* @param errorMessage pointer to buffer fo error message
* @note added in 3.0
*/
static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
const QString& fileName,
const SaveVectorOptions& options,
QString *newFilename = nullptr,
QString *errorMessage = nullptr );
/** Create a new vector file writer */ /** Create a new vector file writer */
QgsVectorFileWriter( const QString& vectorFileName, QgsVectorFileWriter( const QString& vectorFileName,
const QString& fileEncoding, const QString& fileEncoding,
@ -369,6 +490,28 @@ class CORE_EXPORT QgsVectorFileWriter
*/ */
static OGRwkbGeometryType ogrTypeFromWkbType( QgsWkbTypes::Type type ); static OGRwkbGeometryType ogrTypeFromWkbType( QgsWkbTypes::Type type );
/**
* Return edition capabilites for an existing dataset name.
* @note added in QGIS 3.0
*/
static EditionCapabilities editionCapabilities( const QString& datasetName );
/**
* Returns whether the target layer already exists.
* @note added in QGIS 3.0
*/
static bool targetLayerExists( const QString& datasetName,
const QString& layerName );
/**
* Returns whether there are among the attributes specified some that do not exist yet in the layer
* @note added in QGIS 3.0
*/
static bool areThereNewFieldsToCreate( const QString& datasetName,
const QString& layerName,
QgsVectorLayer* layer,
const QgsAttributeList& attributes );
protected: protected:
//! @note not available in python bindings //! @note not available in python bindings
OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType ); OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType );
@ -420,6 +563,8 @@ class CORE_EXPORT QgsVectorFileWriter
* @param newFilename potentially modified file name (output parameter) * @param newFilename potentially modified file name (output parameter)
* @param symbologyExport symbology to export * @param symbologyExport symbology to export
* @param fieldValueConverter field value converter (added in QGIS 2.16) * @param fieldValueConverter field value converter (added in QGIS 2.16)
* @param layerName layer name. If let empty, it will be derived from the filename (added in QGIS 3.0)
* @param action action on existing file (added in QGIS 3.0)
*/ */
QgsVectorFileWriter( const QString& vectorFileName, QgsVectorFileWriter( const QString& vectorFileName,
const QString& fileEncoding, const QString& fileEncoding,
@ -431,14 +576,18 @@ class CORE_EXPORT QgsVectorFileWriter
const QStringList &layerOptions, const QStringList &layerOptions,
QString *newFilename, QString *newFilename,
SymbologyExport symbologyExport, SymbologyExport symbologyExport,
FieldValueConverter* fieldValueConverter FieldValueConverter* fieldValueConverter,
const QString& layerName,
ActionOnExistingFile action
); );
void init( QString vectorFileName, QString fileEncoding, const QgsFields& fields, void init( QString vectorFileName, QString fileEncoding, const QgsFields& fields,
QgsWkbTypes::Type geometryType, QgsCoordinateReferenceSystem srs, QgsWkbTypes::Type geometryType, QgsCoordinateReferenceSystem srs,
const QString& driverName, QStringList datasourceOptions, const QString& driverName, QStringList datasourceOptions,
QStringList layerOptions, QString* newFilename, QStringList layerOptions, QString* newFilename,
FieldValueConverter* fieldValueConverter ); FieldValueConverter* fieldValueConverter,
const QString& layerName,
ActionOnExistingFile action );
void resetMap( const QgsAttributeList &attributes ); void resetMap( const QgsAttributeList &attributes );
QgsRenderContext mRenderContext; QgsRenderContext mRenderContext;
@ -467,4 +616,6 @@ class CORE_EXPORT QgsVectorFileWriter
static QStringList concatenateOptions( const QMap<QString, Option*>& options ); static QStringList concatenateOptions( const QMap<QString, Option*>& options );
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorFileWriter::EditionCapabilities )
#endif #endif

View File

@ -23,21 +23,21 @@
<item> <item>
<widget class="QWidget" name="widget" native="true"> <widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<item row="2" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>CRS</string> <string>CRS</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="2">
<widget class="QLineEdit" name="leFilename"> <widget class="QLineEdit" name="leFilename">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="1" column="3">
<widget class="QPushButton" name="browseFilename"> <widget class="QPushButton" name="browseFilename">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
@ -57,26 +57,43 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="2"> <item row="0" column="2" colspan="2">
<widget class="QComboBox" name="mFormatComboBox"/> <widget class="QComboBox" name="mFormatComboBox"/>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Save as</string> <string>File name</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>leFilename</cstring> <cstring>leFilename</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" colspan="2"> <item row="3" column="2" colspan="2">
<widget class="QgsProjectionSelectionWidget" name="mCrsSelector" native="true"> <widget class="QgsProjectionSelectionWidget" name="mCrsSelector" native="true">
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::StrongFocus</enum> <enum>Qt::StrongFocus</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Layer name</string>
</property>
<property name="buddy">
<cstring>leFilename</cstring>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QLineEdit" name="leLayername">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -90,8 +107,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>550</width> <width>557</width>
<height>953</height> <height>922</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -434,6 +451,7 @@
<tabstop>mFormatComboBox</tabstop> <tabstop>mFormatComboBox</tabstop>
<tabstop>leFilename</tabstop> <tabstop>leFilename</tabstop>
<tabstop>browseFilename</tabstop> <tabstop>browseFilename</tabstop>
<tabstop>leLayername</tabstop>
<tabstop>mCrsSelector</tabstop> <tabstop>mCrsSelector</tabstop>
<tabstop>scrollArea</tabstop> <tabstop>scrollArea</tabstop>
<tabstop>mEncodingComboBox</tabstop> <tabstop>mEncodingComboBox</tabstop>

View File

@ -29,6 +29,7 @@ from qgis.core import (QgsVectorLayer,
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir
import os import os
import osgeo.gdal import osgeo.gdal
from osgeo import gdal, ogr
import platform import platform
from qgis.testing import start_app, unittest from qgis.testing import start_app, unittest
from utilities import writeShape, compareWkt from utilities import writeShape, compareWkt
@ -485,6 +486,177 @@ class TestQgsVectorLayer(unittest.TestCase):
options = QgsVectorFileWriter.defaultLayerOptions('GML') options = QgsVectorFileWriter.defaultLayerOptions('GML')
self.assertEqual(options, []) self.assertEqual(options, [])
def testOverwriteLayer(self):
"""Tests writing a layer with a field value converter."""
ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([1])
provider.addFeatures([ft])
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename, update=1)
lyr = ds.GetLayerByName('test')
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 1)
ds.CreateLayer('another_layer')
del f
del lyr
del ds
caps = QgsVectorFileWriter.editionCapabilities(filename)
self.assertTrue((caps & QgsVectorFileWriter.CanAddNewLayer))
self.assertTrue((caps & QgsVectorFileWriter.CanAppendToExistingLayer))
self.assertTrue((caps & QgsVectorFileWriter.CanAddNewFieldsToExistingLayer))
self.assertTrue((caps & QgsVectorFileWriter.CanDeleteLayer))
self.assertTrue(QgsVectorFileWriter.targetLayerExists(filename, 'test'))
self.assertFalse(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0]))
# Test CreateOrOverwriteLayer
ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([2])
provider.addFeatures([ft])
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 2)
# another_layer should still exist
self.assertIsNotNone(ds.GetLayerByName('another_layer'))
del f
del lyr
del ds
# Test CreateOrOverwriteFile
ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([3])
provider.addFeatures([ft])
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 3)
# another_layer should no longer exist
self.assertIsNone(ds.GetLayerByName('another_layer'))
del f
del lyr
del ds
# Test AppendToLayerNoNewFields
ml = QgsVectorLayer('Point?field=firstfield:int&field=secondfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([4, -10])
provider.addFeatures([ft])
self.assertTrue(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0, 1]))
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
options.actionOnExistingFile = QgsVectorFileWriter.AppendToLayerNoNewFields
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertEqual(lyr.GetLayerDefn().GetFieldCount(), 1)
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 3)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 4)
del f
del lyr
del ds
# Test AppendToLayerAddFields
ml = QgsVectorLayer('Point?field=firstfield:int&field=secondfield:int', 'test', 'memory')
provider = ml.dataProvider()
ft = QgsFeature()
ft.setAttributes([5, -1])
provider.addFeatures([ft])
self.assertTrue(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0, 1]))
options = QgsVectorFileWriter.SaveVectorOptions()
options.driverName = 'GPKG'
options.layerName = 'test'
options.actionOnExistingFile = QgsVectorFileWriter.AppendToLayerAddFields
filename = '/vsimem/out.gpkg'
write_result = QgsVectorFileWriter.writeAsVectorFormat(
ml,
filename,
options)
self.assertEqual(write_result, QgsVectorFileWriter.NoError)
ds = ogr.Open(filename)
lyr = ds.GetLayerByName('test')
self.assertEqual(lyr.GetLayerDefn().GetFieldCount(), 2)
self.assertIsNotNone(lyr)
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 3)
self.assertFalse(f.IsFieldSet('secondfield'))
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 4)
self.assertFalse(f.IsFieldSet('secondfield'))
f = lyr.GetNextFeature()
self.assertEqual(f['firstfield'], 5)
self.assertEqual(f['secondfield'], -1)
del f
del lyr
del ds
gdal.Unlink(filename)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()