mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-30 00:07:09 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			452 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			452 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| /***************************************************************************
 | |
| Name                 : DB Manager
 | |
| Description          : Database manager plugin for QGIS
 | |
| Date                 : Oct 13, 2011
 | |
| copyright            : (C) 2011 by Giuseppe Sucameli
 | |
| email                : brush.tyler@gmail.com
 | |
| 
 | |
| The content of this file is based on
 | |
| - 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.                                   *
 | |
|  *                                                                         *
 | |
|  ***************************************************************************/
 | |
| """
 | |
| 
 | |
| from qgis.PyQt import uic
 | |
| from qgis.PyQt.QtCore import Qt, QFileInfo
 | |
| from qgis.PyQt.QtWidgets import QDialog, QFileDialog, QMessageBox
 | |
| 
 | |
| from qgis.core import (
 | |
|     QgsDataSourceUri,
 | |
|     QgsVectorDataProvider,
 | |
|     QgsVectorLayer,
 | |
|     QgsMapLayerType,
 | |
|     QgsProviderRegistry,
 | |
|     QgsCoordinateReferenceSystem,
 | |
|     QgsVectorLayerExporter,
 | |
|     QgsProject,
 | |
|     QgsSettings,
 | |
| )
 | |
| from qgis.gui import QgsMessageViewer
 | |
| from qgis.utils import OverrideCursor, iface
 | |
| 
 | |
| from .gui_utils import GuiUtils
 | |
| 
 | |
| Ui_Dialog, _ = uic.loadUiType(GuiUtils.get_ui_file_path("DlgImportVector.ui"))
 | |
| 
 | |
| 
 | |
| class DlgImportVector(QDialog, Ui_Dialog):
 | |
|     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)
 | |
| 
 | |
|         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)
 | |
|         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.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)
 | |
|         if not self.chkGeomColumn.isEnabled():
 | |
|             self.chkGeomColumn.setChecked(False)
 | |
| 
 | |
|         self.chkSourceSrid.setEnabled(allowSpatial and hasGeomType)
 | |
|         if not self.chkSourceSrid.isEnabled():
 | |
|             self.chkSourceSrid.setChecked(False)
 | |
|         self.chkTargetSrid.setEnabled(allowSpatial and hasGeomType)
 | |
|         if not self.chkTargetSrid.isEnabled():
 | |
|             self.chkTargetSrid.setChecked(False)
 | |
| 
 | |
|         self.chkSinglePart.setEnabled(allowSpatial and hasGeomType and isShapefile)
 | |
|         if not self.chkSinglePart.isEnabled():
 | |
|             self.chkSinglePart.setChecked(False)
 | |
| 
 | |
|         self.chkSpatialIndex.setEnabled(allowSpatial and hasGeomType)
 | |
|         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 is not None and layer.type() == QgsMapLayerType.VectorLayer:
 | |
|                 self.cboInputLayer.addItem(layer.name(), layer.id())
 | |
| 
 | |
|         # set the current index of the combo box to the active layer in the layer tree (if found in combo box)
 | |
|         if iface is not None and iface.activeLayer():
 | |
|             index = self.cboInputLayer.findData(iface.activeLayer().id())
 | |
|             if index != -1:
 | |
|                 self.cboInputLayer.setCurrentIndex(index)
 | |
| 
 | |
|     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):
 | |
|         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
 | |
| 
 | |
|             layerName = QFileInfo(filename).completeBaseName()
 | |
|             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()
 | |
|         if not srcCrs.isValid():
 | |
|             srcCrs = QgsCoordinateReferenceSystem("EPSG: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()
 | |
|             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):
 | |
|         # populate the combo with supported encodings
 | |
|         self.cboEncoding.addItems(QgsVectorDataProvider.availableEncodings())
 | |
| 
 | |
|         self.cboEncoding.insertItem(0, self.tr("Automatic"), "")
 | |
|         self.cboEncoding.setCurrentIndex(0)
 | |
| 
 | |
|     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()
 | |
| 
 | |
|         # sanity checks
 | |
|         if self.inLayer is None:
 | |
|             QMessageBox.critical(
 | |
|                 self,
 | |
|                 self.tr("Import to Database"),
 | |
|                 self.tr("Input layer missing or not valid."),
 | |
|             )
 | |
|             return
 | |
| 
 | |
|         if self.cboTable.currentText() == "":
 | |
|             QMessageBox.critical(
 | |
|                 self,
 | |
|                 self.tr("Import to Database"),
 | |
|                 self.tr("Output table name is required."),
 | |
|             )
 | |
|             return
 | |
| 
 | |
|         if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
 | |
|             if not self.widgetSourceSrid.crs().isValid():
 | |
|                 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():
 | |
|             if not self.widgetTargetSrid.crs().isValid():
 | |
|                 QMessageBox.critical(
 | |
|                     self,
 | |
|                     self.tr("Import to Database"),
 | |
|                     self.tr("Invalid target srid: must be a valid crs."),
 | |
|                 )
 | |
|                 return
 | |
| 
 | |
|         with OverrideCursor(Qt.CursorShape.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():
 | |
|                     outCrs = self.widgetTargetSrid.crs()
 | |
| 
 | |
|                 # update input layer crs and encoding
 | |
|                 if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
 | |
|                     inCrs = self.widgetSourceSrid.crs()
 | |
|                     self.inLayer.setCrs(inCrs)
 | |
| 
 | |
|                 if (
 | |
|                     self.chkEncoding.isEnabled()
 | |
|                     and self.chkEncoding.isChecked()
 | |
|                     and self.cboEncoding.currentData() is None
 | |
|                 ):
 | |
|                     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:
 | |
|             output = QgsMessageViewer()
 | |
|             output.setTitle(self.tr("Import to Database"))
 | |
|             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)
 | |
|             com = self.editCom.toPlainText()
 | |
|             self.db.connector.commentTable(schema, table, com)
 | |
| 
 | |
|         self.db.connection().reconnect()
 | |
|         self.db.refresh()
 | |
|         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)
 |