mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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:
parent
a5ffc6a856
commit
34894c6f5a
@ -132,6 +132,43 @@ class QgsVectorFileWriter
|
||||
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
|
||||
* @param layer layer to write
|
||||
* @param fileName file name to write to
|
||||
@ -220,6 +257,88 @@ class QgsVectorFileWriter
|
||||
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 */
|
||||
QgsVectorFileWriter( const QString& vectorFileName,
|
||||
const QString& fileEncoding,
|
||||
@ -294,6 +413,27 @@ class QgsVectorFileWriter
|
||||
*/
|
||||
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:
|
||||
//! @note not available in python bindings
|
||||
// OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType );
|
||||
@ -302,3 +442,5 @@ class QgsVectorFileWriter
|
||||
|
||||
QgsVectorFileWriter( const QgsVectorFileWriter& rh );
|
||||
};
|
||||
|
||||
QFlags<QgsVectorFileWriter::EditionCapability> operator|(QgsVectorFileWriter::EditionCapability f1, QFlags<QgsVectorFileWriter::EditionCapability> f2);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "qgseditorwidgetfactory.h"
|
||||
#include "qgseditorwidgetregistry.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
#include <QFileDialog>
|
||||
#include <QTextCodec>
|
||||
@ -37,6 +38,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( long srsid, QWidget* par
|
||||
, mLayer( 0 )
|
||||
, mAttributeTableItemChangedSlotEnabled( true )
|
||||
, mReplaceRawFieldValuesStateChangedSlotEnabled( true )
|
||||
, mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
|
||||
{
|
||||
setup();
|
||||
}
|
||||
@ -46,6 +48,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( QgsVectorLayer *layer, i
|
||||
, mLayer( layer )
|
||||
, mAttributeTableItemChangedSlotEnabled( true )
|
||||
, mReplaceRawFieldValuesStateChangedSlotEnabled( true )
|
||||
, mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
|
||||
{
|
||||
if ( layer )
|
||||
{
|
||||
@ -210,6 +213,121 @@ QgsVectorLayerSaveAsDialog::~QgsVectorLayerSaveAsDialog()
|
||||
|
||||
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;
|
||||
settings.setValue( "/UI/lastVectorFileFilterDir", QFileInfo( filename() ).absolutePath() );
|
||||
settings.setValue( "/UI/lastVectorFormat", format() );
|
||||
@ -226,12 +344,13 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
|
||||
bool selectAllFields = true;
|
||||
bool fieldsAsDisplayedValues = false;
|
||||
|
||||
if ( format() == "KML" )
|
||||
const QString sFormat( format() );
|
||||
if ( sFormat == "KML" )
|
||||
{
|
||||
mAttributesSelection->setEnabled( true );
|
||||
selectAllFields = false;
|
||||
}
|
||||
else if ( format() == "DXF" )
|
||||
else if ( sFormat == "DXF" )
|
||||
{
|
||||
mAttributesSelection->setEnabled( false );
|
||||
selectAllFields = false;
|
||||
@ -239,7 +358,23 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
|
||||
else
|
||||
{
|
||||
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 )
|
||||
@ -485,7 +620,14 @@ void QgsVectorLayerSaveAsDialog::on_mAttributeTable_itemChanged( QTableWidgetIte
|
||||
|
||||
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()
|
||||
@ -493,7 +635,7 @@ void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
|
||||
QSettings settings;
|
||||
QString dirName = leFilename->text().isEmpty() ? settings.value( "/UI/lastVectorFileFilterDir", QDir::homePath() ).toString() : leFilename->text();
|
||||
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() )
|
||||
{
|
||||
leFilename->setText( outputFile );
|
||||
@ -511,6 +653,11 @@ QString QgsVectorLayerSaveAsDialog::filename() const
|
||||
return leFilename->text();
|
||||
}
|
||||
|
||||
QString QgsVectorLayerSaveAsDialog::layername() const
|
||||
{
|
||||
return leLayername->text();
|
||||
}
|
||||
|
||||
QString QgsVectorLayerSaveAsDialog::encoding() const
|
||||
{
|
||||
return mEncodingComboBox->currentText();
|
||||
@ -611,7 +758,7 @@ QStringList QgsVectorLayerSaveAsDialog::layerOptions() const
|
||||
case QgsVectorFileWriter::String:
|
||||
{
|
||||
QLineEdit* le = mLayerOptionsGroupBox->findChild<QLineEdit*>( it.key() );
|
||||
if ( le )
|
||||
if ( le && !le->text().isEmpty() )
|
||||
options << QString( "%1=%2" ).arg( it.key(), le->text() );
|
||||
break;
|
||||
}
|
||||
@ -730,6 +877,11 @@ bool QgsVectorLayerSaveAsDialog::includeZ() const
|
||||
return mIncludeZCheckBox->isChecked();
|
||||
}
|
||||
|
||||
QgsVectorFileWriter::ActionOnExistingFile QgsVectorLayerSaveAsDialog::creationActionOnExistingFile() const
|
||||
{
|
||||
return mActionOnExistingFile;
|
||||
}
|
||||
|
||||
void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked )
|
||||
{
|
||||
mIncludeZCheckBox->setChecked( checked );
|
||||
|
@ -48,6 +48,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
|
||||
QString format() const;
|
||||
QString encoding() const;
|
||||
QString filename() const;
|
||||
QString layername() const;
|
||||
QStringList datasourceOptions() const;
|
||||
QStringList layerOptions() const;
|
||||
long crs() const;
|
||||
@ -100,6 +101,9 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
|
||||
*/
|
||||
void setIncludeZ( bool checked );
|
||||
|
||||
/** Returns creation action */
|
||||
QgsVectorFileWriter::ActionOnExistingFile creationActionOnExistingFile() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void on_mFormatComboBox_currentIndexChanged( int idx );
|
||||
@ -126,6 +130,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
|
||||
QgsVectorLayer *mLayer;
|
||||
bool mAttributeTableItemChangedSlotEnabled;
|
||||
bool mReplaceRawFieldValuesStateChangedSlotEnabled;
|
||||
QgsVectorFileWriter::ActionOnExistingFile mActionOnExistingFile;
|
||||
};
|
||||
|
||||
#endif // QGSVECTORLAYERSAVEASDIALOG_H
|
||||
|
@ -3521,7 +3521,11 @@ bool QgisApp::addVectorLayers( const QStringList &theLayerQStringList, const QSt
|
||||
QString base;
|
||||
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();
|
||||
|
||||
// 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
|
||||
if ( !dialog->attributesAsDisplayedValues().isEmpty() )
|
||||
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(
|
||||
vlayer, vectorFilename, encoding, ct, format,
|
||||
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
|
||||
);
|
||||
vlayer, vectorFilename, options, &newFilename, &errorMessage );
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
@ -6251,7 +6262,10 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
|
||||
{
|
||||
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 );
|
||||
messageBar()->pushMessage( tr( "Saving done" ),
|
||||
|
@ -98,7 +98,8 @@ QgsVectorFileWriter::QgsVectorFileWriter(
|
||||
, mFieldValueConverter( nullptr )
|
||||
{
|
||||
init( theVectorFileName, theFileEncoding, fields, geometryType ,
|
||||
srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr );
|
||||
srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr,
|
||||
QString(), CreateOrOverwriteFile );
|
||||
}
|
||||
|
||||
QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
|
||||
@ -111,7 +112,9 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
|
||||
const QStringList& layerOptions,
|
||||
QString* newFilename,
|
||||
QgsVectorFileWriter::SymbologyExport symbologyExport,
|
||||
FieldValueConverter* fieldValueConverter )
|
||||
FieldValueConverter* fieldValueConverter,
|
||||
const QString& layerName,
|
||||
ActionOnExistingFile action )
|
||||
: mDS( nullptr )
|
||||
, mLayer( nullptr )
|
||||
, mOgrRef( nullptr )
|
||||
@ -124,7 +127,8 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
|
||||
, mFieldValueConverter( nullptr )
|
||||
{
|
||||
init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName,
|
||||
datasourceOptions, layerOptions, newFilename, fieldValueConverter );
|
||||
datasourceOptions, layerOptions, newFilename, fieldValueConverter,
|
||||
layerName, action );
|
||||
}
|
||||
|
||||
void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
@ -136,7 +140,9 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
QStringList datasourceOptions,
|
||||
QStringList layerOptions,
|
||||
QString* newFilename,
|
||||
FieldValueConverter* fieldValueConverter )
|
||||
FieldValueConverter* fieldValueConverter,
|
||||
const QString& layerNameIn,
|
||||
ActionOnExistingFile action )
|
||||
{
|
||||
mRenderContext.setRendererScale( mSymbologyScaleDenominator );
|
||||
|
||||
@ -224,7 +230,8 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
}
|
||||
#endif
|
||||
|
||||
deleteShapeFile( vectorFileName );
|
||||
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
|
||||
deleteShapeFile( vectorFileName );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -247,7 +254,27 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
}
|
||||
}
|
||||
|
||||
QFile::remove( 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( metadataFound && !metadata.compulsoryEncoding.isEmpty() )
|
||||
@ -272,7 +299,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
}
|
||||
|
||||
// create the data source
|
||||
mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
|
||||
if ( action == CreateOrOverwriteFile )
|
||||
mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
|
||||
else
|
||||
mDS = OGROpen( TO8F( vectorFileName ), TRUE, nullptr );
|
||||
|
||||
if ( options )
|
||||
{
|
||||
@ -285,12 +315,43 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
if ( !mDS )
|
||||
{
|
||||
mError = ErrCreateDataSource;
|
||||
mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
|
||||
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
|
||||
if ( action == CreateOrOverwriteFile )
|
||||
mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
|
||||
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
|
||||
else
|
||||
mErrorMessage = QObject::tr( "opening of data source in update mode failed (OGR error:%1)" )
|
||||
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
|
||||
return;
|
||||
}
|
||||
|
||||
QgsDebugMsg( "Created data source" );
|
||||
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" );
|
||||
else
|
||||
QgsDebugMsg( "Opened data source in update mode" );
|
||||
|
||||
// use appropriate codec
|
||||
mCodec = QTextCodec::codecForName( fileEncoding.toLocal8Bit().constData() );
|
||||
@ -318,7 +379,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
}
|
||||
|
||||
// datasource created, now create the output layer
|
||||
QString layerName = QFileInfo( vectorFileName ).baseName();
|
||||
OGRwkbGeometryType wkbType = ogrTypeFromWkbType( geometryType );
|
||||
|
||||
// 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
|
||||
CPLSetConfigOption( "SHAPE_ENCODING", "" );
|
||||
|
||||
mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
|
||||
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
|
||||
mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
|
||||
else
|
||||
mLayer = OGR_DS_GetLayerByName( mDS, TO8F( layerName ) );
|
||||
|
||||
if ( options )
|
||||
{
|
||||
@ -378,8 +441,12 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
|
||||
if ( !mLayer )
|
||||
{
|
||||
mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
|
||||
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
|
||||
if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
|
||||
mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
|
||||
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
|
||||
else
|
||||
mErrorMessage = QObject::tr( "opening of layer failed (OGR error:%1)" )
|
||||
.arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
|
||||
mError = ErrCreateLayer;
|
||||
return;
|
||||
}
|
||||
@ -397,17 +464,30 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
|
||||
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 );
|
||||
|
||||
OGRFieldType ogrType = OFTString; //default to string
|
||||
|
||||
if ( fieldValueConverter )
|
||||
{
|
||||
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 ogrPrecision = attrField.precision();
|
||||
if ( ogrPrecision > 0 )
|
||||
@ -486,8 +566,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
return;
|
||||
}
|
||||
|
||||
QString name( attrField.name() );
|
||||
|
||||
if ( mOgrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 )
|
||||
{
|
||||
int i;
|
||||
@ -581,6 +659,18 @@ void QgsVectorFileWriter::init( QString vectorFileName,
|
||||
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" );
|
||||
|
||||
mWkbType = geometryType;
|
||||
@ -2072,7 +2162,8 @@ void QgsVectorFileWriter::resetMap( const QgsAttributeList &attributes )
|
||||
mAttrIdxToOgrIdx.clear();
|
||||
for ( int i = 0; i < attributes.size(); i++ )
|
||||
{
|
||||
mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
|
||||
if ( omap.find( i ) != omap.end() )
|
||||
mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
|
||||
}
|
||||
}
|
||||
|
||||
@ -2161,6 +2252,57 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
bool includeZ,
|
||||
QgsAttributeList attributes,
|
||||
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 )
|
||||
{
|
||||
@ -2169,10 +2311,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
|
||||
bool shallTransform = false;
|
||||
QgsCoordinateReferenceSystem outputCRS;
|
||||
if ( ct.isValid() )
|
||||
if ( options.ct.isValid() )
|
||||
{
|
||||
// This means we should transform
|
||||
outputCRS = ct.destinationCrs();
|
||||
outputCRS = options.ct.destinationCrs();
|
||||
shallTransform = true;
|
||||
}
|
||||
else
|
||||
@ -2182,18 +2324,19 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
}
|
||||
|
||||
QgsWkbTypes::Type destWkbType = layer->wkbType();
|
||||
if ( overrideGeometryType != QgsWkbTypes::Unknown )
|
||||
if ( options.overrideGeometryType != QgsWkbTypes::Unknown )
|
||||
{
|
||||
destWkbType = QgsWkbTypes::flatType( overrideGeometryType );
|
||||
if ( QgsWkbTypes::hasZ( overrideGeometryType ) || includeZ )
|
||||
destWkbType = QgsWkbTypes::flatType( options.overrideGeometryType );
|
||||
if ( QgsWkbTypes::hasZ( options.overrideGeometryType ) || options.includeZ )
|
||||
destWkbType = QgsWkbTypes::addZ( destWkbType );
|
||||
}
|
||||
if ( forceMulti )
|
||||
if ( options.forceMulti )
|
||||
{
|
||||
destWkbType = QgsWkbTypes::multiType( destWkbType );
|
||||
}
|
||||
|
||||
if ( skipAttributeCreation )
|
||||
QgsAttributeList attributes( options.attributes );
|
||||
if ( options.skipAttributeCreation )
|
||||
attributes.clear();
|
||||
else if ( attributes.isEmpty() )
|
||||
{
|
||||
@ -2231,7 +2374,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
if ( layer->storageType() == "ESRI Shapefile" && !QgsWkbTypes::isMultiType( destWkbType ) )
|
||||
{
|
||||
QgsFeatureRequest req;
|
||||
if ( onlySelected )
|
||||
if ( options.onlySelectedFeatures )
|
||||
{
|
||||
req.setFilterFids( layer->selectedFeaturesIds() );
|
||||
}
|
||||
@ -2265,11 +2408,17 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
}
|
||||
|
||||
QgsVectorFileWriter* writer =
|
||||
new QgsVectorFileWriter( fileName, fileEncoding, fields, destWkbType,
|
||||
outputCRS, driverName, datasourceOptions, layerOptions,
|
||||
newFilename, symbologyExport,
|
||||
fieldValueConverter );
|
||||
writer->setSymbologyScaleDenominator( symbologyScale );
|
||||
new QgsVectorFileWriter( fileName,
|
||||
options.fileEncoding, fields, destWkbType,
|
||||
outputCRS, options.driverName,
|
||||
options.datasourceOptions,
|
||||
options.layerOptions,
|
||||
newFilename,
|
||||
options.symbologyExport,
|
||||
options.fieldValueConverter,
|
||||
options.layerName,
|
||||
options.actionOnExistingFile );
|
||||
writer->setSymbologyScaleDenominator( options.symbologyScale );
|
||||
|
||||
if ( newFilename )
|
||||
{
|
||||
@ -2302,7 +2451,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
req.setFlags( QgsFeatureRequest::NoGeometry );
|
||||
}
|
||||
req.setSubsetOfAttributes( attributes );
|
||||
if ( onlySelected )
|
||||
if ( options.onlySelectedFeatures )
|
||||
req.setFilterFids( layer->selectedFeaturesIds() );
|
||||
QgsFeatureIterator fit = layer->getFeatures( req );
|
||||
|
||||
@ -2318,7 +2467,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
if ( r->capabilities() & QgsFeatureRenderer::SymbolLevels
|
||||
&& r->usingSymbolLevels() )
|
||||
{
|
||||
QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, ct, errorMessage );
|
||||
QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, options.ct, errorMessage );
|
||||
delete writer;
|
||||
return ( error == NoError ) ? NoError : ErrFeatureWriteFailed;
|
||||
}
|
||||
@ -2328,9 +2477,9 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
|
||||
//unit type
|
||||
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 );
|
||||
@ -2358,7 +2507,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
|
||||
if ( fet.hasGeometry() )
|
||||
{
|
||||
QgsGeometry g = fet.geometry();
|
||||
g.transform( ct );
|
||||
g.transform( options.ct );
|
||||
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;
|
||||
|
||||
if ( attributes.size() < 1 && skipAttributeCreation )
|
||||
if ( attributes.size() < 1 && options.skipAttributeCreation )
|
||||
{
|
||||
fet.initAttributes( 0 );
|
||||
}
|
||||
@ -2927,3 +3076,91 @@ QStringList QgsVectorFileWriter::concatenateOptions( const QMap<QString, QgsVect
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -199,6 +199,45 @@ class CORE_EXPORT QgsVectorFileWriter
|
||||
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
|
||||
* @param layer layer to write
|
||||
* @param fileName file name to write to
|
||||
@ -287,6 +326,88 @@ class CORE_EXPORT QgsVectorFileWriter
|
||||
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 */
|
||||
QgsVectorFileWriter( const QString& vectorFileName,
|
||||
const QString& fileEncoding,
|
||||
@ -369,6 +490,28 @@ class CORE_EXPORT QgsVectorFileWriter
|
||||
*/
|
||||
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:
|
||||
//! @note not available in python bindings
|
||||
OGRGeometryH createEmptyGeometry( QgsWkbTypes::Type wkbType );
|
||||
@ -420,6 +563,8 @@ class CORE_EXPORT QgsVectorFileWriter
|
||||
* @param newFilename potentially modified file name (output parameter)
|
||||
* @param symbologyExport symbology to export
|
||||
* @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,
|
||||
const QString& fileEncoding,
|
||||
@ -431,14 +576,18 @@ class CORE_EXPORT QgsVectorFileWriter
|
||||
const QStringList &layerOptions,
|
||||
QString *newFilename,
|
||||
SymbologyExport symbologyExport,
|
||||
FieldValueConverter* fieldValueConverter
|
||||
FieldValueConverter* fieldValueConverter,
|
||||
const QString& layerName,
|
||||
ActionOnExistingFile action
|
||||
);
|
||||
|
||||
void init( QString vectorFileName, QString fileEncoding, const QgsFields& fields,
|
||||
QgsWkbTypes::Type geometryType, QgsCoordinateReferenceSystem srs,
|
||||
const QString& driverName, QStringList datasourceOptions,
|
||||
QStringList layerOptions, QString* newFilename,
|
||||
FieldValueConverter* fieldValueConverter );
|
||||
FieldValueConverter* fieldValueConverter,
|
||||
const QString& layerName,
|
||||
ActionOnExistingFile action );
|
||||
void resetMap( const QgsAttributeList &attributes );
|
||||
|
||||
QgsRenderContext mRenderContext;
|
||||
@ -467,4 +616,6 @@ class CORE_EXPORT QgsVectorFileWriter
|
||||
static QStringList concatenateOptions( const QMap<QString, Option*>& options );
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorFileWriter::EditionCapabilities )
|
||||
|
||||
#endif
|
||||
|
@ -23,21 +23,21 @@
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>CRS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="leFilename">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="browseFilename">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
@ -57,26 +57,43 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<item row="0" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="mFormatComboBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Save as</string>
|
||||
<string>File name</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>leFilename</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<item row="3" column="2" colspan="2">
|
||||
<widget class="QgsProjectionSelectionWidget" name="mCrsSelector" native="true">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
@ -90,8 +107,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>550</width>
|
||||
<height>953</height>
|
||||
<width>557</width>
|
||||
<height>922</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@ -434,6 +451,7 @@
|
||||
<tabstop>mFormatComboBox</tabstop>
|
||||
<tabstop>leFilename</tabstop>
|
||||
<tabstop>browseFilename</tabstop>
|
||||
<tabstop>leLayername</tabstop>
|
||||
<tabstop>mCrsSelector</tabstop>
|
||||
<tabstop>scrollArea</tabstop>
|
||||
<tabstop>mEncodingComboBox</tabstop>
|
||||
|
@ -29,6 +29,7 @@ from qgis.core import (QgsVectorLayer,
|
||||
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir
|
||||
import os
|
||||
import osgeo.gdal
|
||||
from osgeo import gdal, ogr
|
||||
import platform
|
||||
from qgis.testing import start_app, unittest
|
||||
from utilities import writeShape, compareWkt
|
||||
@ -485,6 +486,177 @@ class TestQgsVectorLayer(unittest.TestCase):
|
||||
options = QgsVectorFileWriter.defaultLayerOptions('GML')
|
||||
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__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user