QGIS/python/plugins/db_manager/dlg_import_vector.py

394 lines
16 KiB
Python
Raw Normal View History

2012-04-16 13:19:40 +02:00
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : DB Manager
2013-06-09 18:28:52 +02:00
Description : Database manager plugin for QGIS
2012-04-16 13:19:40 +02:00
Date : Oct 13, 2011
copyright : (C) 2011 by Giuseppe Sucameli
email : brush.tyler@gmail.com
2012-12-10 00:12:07 +01:00
The content of this file is based on
2012-04-16 13:19:40 +02:00
- PG_Manager by Martin Dobias (GPLv2 license)
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
2016-09-21 18:24:26 +02:00
from builtins import str
from builtins import range
2012-04-16 13:19:40 +02:00
from qgis.PyQt.QtCore import Qt, QFileInfo
2019-02-06 17:37:48 +01:00
from qgis.PyQt.QtWidgets import QDialog, QFileDialog, QMessageBox
2012-04-16 13:19:40 +02:00
from qgis.core import (QgsDataSourceUri,
QgsVectorLayer,
2019-03-27 07:20:43 +10:00
QgsMapLayerType,
QgsProviderRegistry,
QgsCoordinateReferenceSystem,
QgsVectorLayerExporter,
QgsProject,
QgsSettings)
2018-02-20 11:10:13 +03:00
from qgis.gui import QgsMessageViewer
2017-07-30 22:18:25 +02:00
from qgis.utils import OverrideCursor
2012-04-16 13:19:40 +02:00
from .ui.ui_DlgImportVector import Ui_DbManagerDlgImportVector as Ui_Dialog
2012-04-16 13:19:40 +02:00
2012-12-10 01:48:44 +01:00
class DlgImportVector(QDialog, Ui_Dialog):
2016-09-21 18:24:26 +02:00
HAS_INPUT_MODE, ASK_FOR_INPUT_MODE = list(range(2))
def __init__(self, inLayer, outDb, outUri, parent=None):
QDialog.__init__(self, parent)
self.inLayer = inLayer
self.db = outDb
self.outUri = outUri
self.setupUi(self)
2019-02-06 17:37:48 +01:00
supportCom = self.db.supportsComment()
if not supportCom:
self.chkCom.setVisible(False)
self.editCom.setVisible(False)
self.default_pk = "id"
self.default_geom = "geom"
self.mode = self.ASK_FOR_INPUT_MODE if self.inLayer is None else self.HAS_INPUT_MODE
# used to delete the inlayer whether created inside this dialog
self.inLayerMustBeDestroyed = False
self.populateSchemas()
self.populateTables()
self.populateLayers()
self.populateEncodings()
# updates of UI
self.setupWorkingMode(self.mode)
self.cboSchema.currentIndexChanged.connect(self.populateTables)
2018-02-20 11:10:13 +03:00
self.widgetSourceSrid.setCrs(QgsProject.instance().crs())
self.widgetTargetSrid.setCrs(QgsProject.instance().crs())
self.updateInputLayer()
def setupWorkingMode(self, mode):
""" hide the widget to select a layer/file if the input layer is already set """
self.wdgInput.setVisible(mode == self.ASK_FOR_INPUT_MODE)
self.resize(450, 350)
self.cboTable.setEditText(self.outUri.table())
if mode == self.ASK_FOR_INPUT_MODE:
self.btnChooseInputFile.clicked.connect(self.chooseInputFile)
self.cboInputLayer.currentTextChanged.connect(self.updateInputLayer)
self.btnUpdateInputLayer.clicked.connect(self.updateInputLayer)
self.editPrimaryKey.setText(self.default_pk)
self.editGeomColumn.setText(self.default_geom)
self.chkLowercaseFieldNames.setEnabled(self.db.hasLowercaseFieldNamesOption())
if not self.chkLowercaseFieldNames.isEnabled():
self.chkLowercaseFieldNames.setChecked(False)
else:
# set default values
self.checkSupports()
self.updateInputLayer()
def checkSupports(self):
""" update options available for the current input layer """
allowSpatial = self.db.connector.hasSpatialSupport()
hasGeomType = self.inLayer and self.inLayer.isSpatial()
isShapefile = self.inLayer and self.inLayer.providerType() == "ogr" and self.inLayer.storageType() == "ESRI Shapefile"
self.chkGeomColumn.setEnabled(allowSpatial and hasGeomType)
2015-08-25 20:04:50 +02:00
if not self.chkGeomColumn.isEnabled():
self.chkGeomColumn.setChecked(False)
self.chkSourceSrid.setEnabled(allowSpatial and hasGeomType)
2015-08-25 20:04:50 +02:00
if not self.chkSourceSrid.isEnabled():
self.chkSourceSrid.setChecked(False)
self.chkTargetSrid.setEnabled(allowSpatial and hasGeomType)
2015-08-25 20:04:50 +02:00
if not self.chkTargetSrid.isEnabled():
self.chkTargetSrid.setChecked(False)
self.chkSinglePart.setEnabled(allowSpatial and hasGeomType and isShapefile)
2015-08-25 20:04:50 +02:00
if not self.chkSinglePart.isEnabled():
self.chkSinglePart.setChecked(False)
self.chkSpatialIndex.setEnabled(allowSpatial and hasGeomType)
2015-08-25 20:04:50 +02:00
if not self.chkSpatialIndex.isEnabled():
self.chkSpatialIndex.setChecked(False)
self.chkLowercaseFieldNames.setEnabled(self.db.hasLowercaseFieldNamesOption())
if not self.chkLowercaseFieldNames.isEnabled():
self.chkLowercaseFieldNames.setChecked(False)
def populateLayers(self):
self.cboInputLayer.clear()
for nodeLayer in QgsProject.instance().layerTreeRoot().findLayers():
layer = nodeLayer.layer()
# TODO: add import raster support!
if layer.type() == QgsMapLayerType.VectorLayer:
self.cboInputLayer.addItem(layer.name(), layer.id())
def deleteInputLayer(self):
""" unset the input layer, then destroy it but only if it was created from this dialog """
if self.mode == self.ASK_FOR_INPUT_MODE and self.inLayer:
if self.inLayerMustBeDestroyed:
self.inLayer.deleteLater()
self.inLayer = None
self.inLayerMustBeDestroyed = False
return True
return False
def chooseInputFile(self):
2016-06-10 22:14:03 +02:00
vectorFormats = QgsProviderRegistry.instance().fileVectorFilters()
# get last used dir and format
settings = QgsSettings()
lastDir = settings.value("/db_manager/lastUsedDir", "")
lastVectorFormat = settings.value("/UI/lastVectorFileFilter", "")
# ask for a filename
filename, lastVectorFormat = QFileDialog.getOpenFileName(self, self.tr("Choose the file to import"),
lastDir, vectorFormats, lastVectorFormat)
if filename == "":
return
# store the last used dir and format
settings.setValue("/db_manager/lastUsedDir", QFileInfo(filename).filePath())
settings.setValue("/UI/lastVectorFileFilter", lastVectorFormat)
self.cboInputLayer.setCurrentIndex(-1)
self.cboInputLayer.setEditText(filename)
def reloadInputLayer(self):
"""Creates the input layer and update available options """
if self.mode != self.ASK_FOR_INPUT_MODE:
return True
self.deleteInputLayer()
index = self.cboInputLayer.currentIndex()
if index < 0:
filename = self.cboInputLayer.currentText()
if filename == "":
return False
2012-04-16 13:19:40 +02:00
layerName = QFileInfo(filename).completeBaseName()
2016-06-10 22:14:03 +02:00
layer = QgsVectorLayer(filename, layerName, "ogr")
if not layer.isValid() or layer.type() != QgsMapLayerType.VectorLayer:
layer.deleteLater()
return False
self.inLayer = layer
self.inLayerMustBeDestroyed = True
else:
layerId = self.cboInputLayer.itemData(index)
self.inLayer = QgsProject.instance().mapLayer(layerId)
self.inLayerMustBeDestroyed = False
self.checkSupports()
return True
def updateInputLayer(self):
if not self.reloadInputLayer() or not self.inLayer:
return False
# update the output table name, pk and geom column
self.cboTable.setEditText(self.inLayer.name())
srcUri = QgsDataSourceUri(self.inLayer.source())
pk = srcUri.keyColumn() if srcUri.keyColumn() else self.default_pk
self.editPrimaryKey.setText(pk)
geom = srcUri.geometryColumn() if srcUri.geometryColumn() else self.default_geom
self.editGeomColumn.setText(geom)
srcCrs = self.inLayer.crs()
2018-02-13 18:04:47 +03:00
if not srcCrs.isValid():
srcCrs = QgsCoordinateReferenceSystem(4326)
self.widgetSourceSrid.setCrs(srcCrs)
self.widgetTargetSrid.setCrs(srcCrs)
return True
def populateSchemas(self):
if not self.db:
return
self.cboSchema.clear()
schemas = self.db.schemas()
if schemas is None:
self.hideSchemas()
return
index = -1
for schema in schemas:
self.cboSchema.addItem(schema.name)
if schema.name == self.outUri.schema():
index = self.cboSchema.count() - 1
self.cboSchema.setCurrentIndex(index)
def hideSchemas(self):
self.cboSchema.setEnabled(False)
def populateTables(self):
if not self.db:
return
currentText = self.cboTable.currentText()
schemas = self.db.schemas()
if schemas is not None:
schema_name = self.cboSchema.currentText()
2016-03-21 04:51:10 +01:00
matching_schemas = [x for x in schemas if x.name == schema_name]
tables = matching_schemas[0].tables() if len(matching_schemas) > 0 else []
else:
tables = self.db.tables()
self.cboTable.clear()
for table in tables:
self.cboTable.addItem(table.name)
self.cboTable.setEditText(currentText)
def populateEncodings(self):
encodings = ['ISO-8859-1', 'ISO-8859-2', 'UTF-8', 'CP1250']
for enc in encodings:
self.cboEncoding.addItem(enc)
self.cboEncoding.setCurrentIndex(2)
def accept(self):
if self.mode == self.ASK_FOR_INPUT_MODE:
# create the input layer (if not already done) and
# update available options
self.reloadInputLayer()
2015-08-25 20:04:50 +02:00
# sanity checks
if self.inLayer is None:
2018-02-14 08:58:08 +03:00
QMessageBox.critical(self, self.tr("Import to Database"), self.tr("Input layer missing or not valid."))
return
if self.cboTable.currentText() == "":
2018-02-14 08:58:08 +03:00
QMessageBox.critical(self, self.tr("Import to Database"), self.tr("Output table name is required."))
return
if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
2018-02-13 18:04:47 +03:00
if not self.widgetSourceSrid.crs().isValid():
2018-02-14 08:58:08 +03:00
QMessageBox.critical(self, self.tr("Import to Database"),
self.tr("Invalid source srid: must be a valid crs."))
return
if self.chkTargetSrid.isEnabled() and self.chkTargetSrid.isChecked():
2018-02-13 18:04:47 +03:00
if not self.widgetTargetSrid.crs().isValid():
2018-02-14 08:58:08 +03:00
QMessageBox.critical(self, self.tr("Import to Database"),
self.tr("Invalid target srid: must be a valid crs."))
return
2017-07-30 22:18:25 +02:00
with OverrideCursor(Qt.WaitCursor):
# store current input layer crs and encoding, so I can restore it
prevInCrs = self.inLayer.crs()
prevInEncoding = self.inLayer.dataProvider().encoding()
try:
schema = self.outUri.schema() if not self.cboSchema.isEnabled() else self.cboSchema.currentText()
table = self.cboTable.currentText()
# get pk and geom field names from the source layer or use the
# ones defined by the user
srcUri = QgsDataSourceUri(self.inLayer.source())
pk = srcUri.keyColumn() if not self.chkPrimaryKey.isChecked() else self.editPrimaryKey.text()
if not pk:
pk = self.default_pk
if self.inLayer.isSpatial() and self.chkGeomColumn.isEnabled():
geom = srcUri.geometryColumn() if not self.chkGeomColumn.isChecked() else self.editGeomColumn.text()
if not geom:
geom = self.default_geom
else:
geom = None
options = {}
if self.chkLowercaseFieldNames.isEnabled() and self.chkLowercaseFieldNames.isChecked():
pk = pk.lower()
if geom:
geom = geom.lower()
options['lowercaseFieldNames'] = True
# get output params, update output URI
self.outUri.setDataSource(schema, table, geom, "", pk)
typeName = self.db.dbplugin().typeName()
providerName = self.db.dbplugin().providerName()
if typeName == 'gpkg':
uri = self.outUri.database()
options['update'] = True
options['driverName'] = 'GPKG'
options['layerName'] = table
else:
uri = self.outUri.uri(False)
if self.chkDropTable.isChecked():
options['overwrite'] = True
if self.chkSinglePart.isEnabled() and self.chkSinglePart.isChecked():
options['forceSinglePartGeometryType'] = True
outCrs = QgsCoordinateReferenceSystem()
if self.chkTargetSrid.isEnabled() and self.chkTargetSrid.isChecked():
2018-02-13 18:04:47 +03:00
outCrs = self.widgetTargetSrid.crs()
2017-07-30 22:18:25 +02:00
# update input layer crs and encoding
if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
2018-02-13 18:04:47 +03:00
inCrs = self.widgetSourceSrid.crs()
2017-07-30 22:18:25 +02:00
self.inLayer.setCrs(inCrs)
if self.chkEncoding.isEnabled() and self.chkEncoding.isChecked():
enc = self.cboEncoding.currentText()
self.inLayer.setProviderEncoding(enc)
onlySelected = self.chkSelectedFeatures.isChecked()
# do the import!
ret, errMsg = QgsVectorLayerExporter.exportLayer(self.inLayer, uri, providerName, outCrs, onlySelected, options)
except Exception as e:
ret = -1
errMsg = str(e)
finally:
# restore input layer crs and encoding
self.inLayer.setCrs(prevInCrs)
self.inLayer.setProviderEncoding(prevInEncoding)
if ret != 0:
2016-06-10 22:14:03 +02:00
output = QgsMessageViewer()
2018-02-14 08:58:08 +03:00
output.setTitle(self.tr("Import to Database"))
2017-03-04 16:23:36 +01:00
output.setMessageAsPlainText(self.tr("Error {0}\n{1}").format(ret, errMsg))
output.showMessage()
return
# create spatial index
if self.chkSpatialIndex.isEnabled() and self.chkSpatialIndex.isChecked():
self.db.connector.createSpatialIndex((schema, table), geom)
# add comment on table
supportCom = self.db.supportsComment()
if self.chkCom.isEnabled() and self.chkCom.isChecked() and supportCom:
# using connector executing COMMENT ON TABLE query (with editCome.text() value)
2019-03-05 09:59:28 +01:00
com = self.editCom.text()
2019-02-08 08:30:48 +01:00
self.db.connector.commentTable(schema, table, com)
self.db.connection().reconnect()
self.db.refresh()
2018-02-14 08:58:08 +03:00
QMessageBox.information(self, self.tr("Import to Database"), self.tr("Import was successful."))
return QDialog.accept(self)
def closeEvent(self, event):
# destroy the input layer instance but only if it was created
# from this dialog!
self.deleteInputLayer()
QDialog.closeEvent(self, event)