dlg_import_vector.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. """
  2. /***************************************************************************
  3. Name : DB Manager
  4. Description : Database manager plugin for QGIS
  5. Date : Oct 13, 2011
  6. copyright : (C) 2011 by Giuseppe Sucameli
  7. email : brush.tyler@gmail.com
  8. The content of this file is based on
  9. - PG_Manager by Martin Dobias (GPLv2 license)
  10. ***************************************************************************/
  11. /***************************************************************************
  12. * *
  13. * This program is free software; you can redistribute it and/or modify *
  14. * it under the terms of the GNU General Public License as published by *
  15. * the Free Software Foundation; either version 2 of the License, or *
  16. * (at your option) any later version. *
  17. * *
  18. ***************************************************************************/
  19. """
  20. from qgis.PyQt.QtCore import Qt, QFileInfo
  21. from qgis.PyQt.QtWidgets import QDialog, QFileDialog, QMessageBox
  22. from qgis.core import (QgsDataSourceUri,
  23. QgsVectorDataProvider,
  24. QgsVectorLayer,
  25. QgsMapLayerType,
  26. QgsProviderRegistry,
  27. QgsCoordinateReferenceSystem,
  28. QgsVectorLayerExporter,
  29. QgsProject,
  30. QgsSettings)
  31. from qgis.gui import QgsMessageViewer
  32. from qgis.utils import OverrideCursor
  33. from .ui.ui_DlgImportVector import Ui_DbManagerDlgImportVector as Ui_Dialog
  34. class DlgImportVector(QDialog, Ui_Dialog):
  35. HAS_INPUT_MODE, ASK_FOR_INPUT_MODE = list(range(2))
  36. def __init__(self, inLayer, outDb, outUri, parent=None):
  37. QDialog.__init__(self, parent)
  38. self.inLayer = inLayer
  39. self.db = outDb
  40. self.outUri = outUri
  41. self.setupUi(self)
  42. supportCom = self.db.supportsComment()
  43. if not supportCom:
  44. self.chkCom.setVisible(False)
  45. self.editCom.setVisible(False)
  46. self.default_pk = "id"
  47. self.default_geom = "geom"
  48. self.mode = self.ASK_FOR_INPUT_MODE if self.inLayer is None else self.HAS_INPUT_MODE
  49. # used to delete the inlayer whether created inside this dialog
  50. self.inLayerMustBeDestroyed = False
  51. self.populateSchemas()
  52. self.populateTables()
  53. self.populateLayers()
  54. self.populateEncodings()
  55. # updates of UI
  56. self.setupWorkingMode(self.mode)
  57. self.cboSchema.currentIndexChanged.connect(self.populateTables)
  58. self.widgetSourceSrid.setCrs(QgsProject.instance().crs())
  59. self.widgetTargetSrid.setCrs(QgsProject.instance().crs())
  60. self.updateInputLayer()
  61. def setupWorkingMode(self, mode):
  62. """ hide the widget to select a layer/file if the input layer is already set """
  63. self.wdgInput.setVisible(mode == self.ASK_FOR_INPUT_MODE)
  64. self.resize(450, 350)
  65. self.cboTable.setEditText(self.outUri.table())
  66. if mode == self.ASK_FOR_INPUT_MODE:
  67. self.btnChooseInputFile.clicked.connect(self.chooseInputFile)
  68. self.cboInputLayer.currentTextChanged.connect(self.updateInputLayer)
  69. self.editPrimaryKey.setText(self.default_pk)
  70. self.editGeomColumn.setText(self.default_geom)
  71. self.chkLowercaseFieldNames.setEnabled(self.db.hasLowercaseFieldNamesOption())
  72. if not self.chkLowercaseFieldNames.isEnabled():
  73. self.chkLowercaseFieldNames.setChecked(False)
  74. else:
  75. # set default values
  76. self.checkSupports()
  77. self.updateInputLayer()
  78. def checkSupports(self):
  79. """ update options available for the current input layer """
  80. allowSpatial = self.db.connector.hasSpatialSupport()
  81. hasGeomType = self.inLayer and self.inLayer.isSpatial()
  82. isShapefile = self.inLayer and self.inLayer.providerType() == "ogr" and self.inLayer.storageType() == "ESRI Shapefile"
  83. self.chkGeomColumn.setEnabled(allowSpatial and hasGeomType)
  84. if not self.chkGeomColumn.isEnabled():
  85. self.chkGeomColumn.setChecked(False)
  86. self.chkSourceSrid.setEnabled(allowSpatial and hasGeomType)
  87. if not self.chkSourceSrid.isEnabled():
  88. self.chkSourceSrid.setChecked(False)
  89. self.chkTargetSrid.setEnabled(allowSpatial and hasGeomType)
  90. if not self.chkTargetSrid.isEnabled():
  91. self.chkTargetSrid.setChecked(False)
  92. self.chkSinglePart.setEnabled(allowSpatial and hasGeomType and isShapefile)
  93. if not self.chkSinglePart.isEnabled():
  94. self.chkSinglePart.setChecked(False)
  95. self.chkSpatialIndex.setEnabled(allowSpatial and hasGeomType)
  96. if not self.chkSpatialIndex.isEnabled():
  97. self.chkSpatialIndex.setChecked(False)
  98. self.chkLowercaseFieldNames.setEnabled(self.db.hasLowercaseFieldNamesOption())
  99. if not self.chkLowercaseFieldNames.isEnabled():
  100. self.chkLowercaseFieldNames.setChecked(False)
  101. def populateLayers(self):
  102. self.cboInputLayer.clear()
  103. for nodeLayer in QgsProject.instance().layerTreeRoot().findLayers():
  104. layer = nodeLayer.layer()
  105. # TODO: add import raster support!
  106. if layer is not None and layer.type() == QgsMapLayerType.VectorLayer:
  107. self.cboInputLayer.addItem(layer.name(), layer.id())
  108. def deleteInputLayer(self):
  109. """ unset the input layer, then destroy it but only if it was created from this dialog """
  110. if self.mode == self.ASK_FOR_INPUT_MODE and self.inLayer:
  111. if self.inLayerMustBeDestroyed:
  112. self.inLayer.deleteLater()
  113. self.inLayer = None
  114. self.inLayerMustBeDestroyed = False
  115. return True
  116. return False
  117. def chooseInputFile(self):
  118. vectorFormats = QgsProviderRegistry.instance().fileVectorFilters()
  119. # get last used dir and format
  120. settings = QgsSettings()
  121. lastDir = settings.value("/db_manager/lastUsedDir", "")
  122. lastVectorFormat = settings.value("/UI/lastVectorFileFilter", "")
  123. # ask for a filename
  124. filename, lastVectorFormat = QFileDialog.getOpenFileName(self, self.tr("Choose the file to import"),
  125. lastDir, vectorFormats, lastVectorFormat)
  126. if filename == "":
  127. return
  128. # store the last used dir and format
  129. settings.setValue("/db_manager/lastUsedDir", QFileInfo(filename).filePath())
  130. settings.setValue("/UI/lastVectorFileFilter", lastVectorFormat)
  131. self.cboInputLayer.setCurrentIndex(-1)
  132. self.cboInputLayer.setEditText(filename)
  133. def reloadInputLayer(self):
  134. """Creates the input layer and update available options """
  135. if self.mode != self.ASK_FOR_INPUT_MODE:
  136. return True
  137. self.deleteInputLayer()
  138. index = self.cboInputLayer.currentIndex()
  139. if index < 0:
  140. filename = self.cboInputLayer.currentText()
  141. if filename == "":
  142. return False
  143. layerName = QFileInfo(filename).completeBaseName()
  144. layer = QgsVectorLayer(filename, layerName, "ogr")
  145. if not layer.isValid() or layer.type() != QgsMapLayerType.VectorLayer:
  146. layer.deleteLater()
  147. return False
  148. self.inLayer = layer
  149. self.inLayerMustBeDestroyed = True
  150. else:
  151. layerId = self.cboInputLayer.itemData(index)
  152. self.inLayer = QgsProject.instance().mapLayer(layerId)
  153. self.inLayerMustBeDestroyed = False
  154. self.checkSupports()
  155. return True
  156. def updateInputLayer(self):
  157. if not self.reloadInputLayer() or not self.inLayer:
  158. return False
  159. # update the output table name, pk and geom column
  160. self.cboTable.setEditText(self.inLayer.name())
  161. srcUri = QgsDataSourceUri(self.inLayer.source())
  162. pk = srcUri.keyColumn() if srcUri.keyColumn() else self.default_pk
  163. self.editPrimaryKey.setText(pk)
  164. geom = srcUri.geometryColumn() if srcUri.geometryColumn() else self.default_geom
  165. self.editGeomColumn.setText(geom)
  166. srcCrs = self.inLayer.crs()
  167. if not srcCrs.isValid():
  168. srcCrs = QgsCoordinateReferenceSystem("EPSG:4326")
  169. self.widgetSourceSrid.setCrs(srcCrs)
  170. self.widgetTargetSrid.setCrs(srcCrs)
  171. return True
  172. def populateSchemas(self):
  173. if not self.db:
  174. return
  175. self.cboSchema.clear()
  176. schemas = self.db.schemas()
  177. if schemas is None:
  178. self.hideSchemas()
  179. return
  180. index = -1
  181. for schema in schemas:
  182. self.cboSchema.addItem(schema.name)
  183. if schema.name == self.outUri.schema():
  184. index = self.cboSchema.count() - 1
  185. self.cboSchema.setCurrentIndex(index)
  186. def hideSchemas(self):
  187. self.cboSchema.setEnabled(False)
  188. def populateTables(self):
  189. if not self.db:
  190. return
  191. currentText = self.cboTable.currentText()
  192. schemas = self.db.schemas()
  193. if schemas is not None:
  194. schema_name = self.cboSchema.currentText()
  195. matching_schemas = [x for x in schemas if x.name == schema_name]
  196. tables = matching_schemas[0].tables() if len(matching_schemas) > 0 else []
  197. else:
  198. tables = self.db.tables()
  199. self.cboTable.clear()
  200. for table in tables:
  201. self.cboTable.addItem(table.name)
  202. self.cboTable.setEditText(currentText)
  203. def populateEncodings(self):
  204. # populate the combo with supported encodings
  205. self.cboEncoding.addItems(QgsVectorDataProvider.availableEncodings())
  206. self.cboEncoding.insertItem(0, self.tr('Automatic'), "")
  207. self.cboEncoding.setCurrentIndex(0)
  208. def accept(self):
  209. if self.mode == self.ASK_FOR_INPUT_MODE:
  210. # create the input layer (if not already done) and
  211. # update available options
  212. self.reloadInputLayer()
  213. # sanity checks
  214. if self.inLayer is None:
  215. QMessageBox.critical(self, self.tr("Import to Database"), self.tr("Input layer missing or not valid."))
  216. return
  217. if self.cboTable.currentText() == "":
  218. QMessageBox.critical(self, self.tr("Import to Database"), self.tr("Output table name is required."))
  219. return
  220. if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
  221. if not self.widgetSourceSrid.crs().isValid():
  222. QMessageBox.critical(self, self.tr("Import to Database"),
  223. self.tr("Invalid source srid: must be a valid crs."))
  224. return
  225. if self.chkTargetSrid.isEnabled() and self.chkTargetSrid.isChecked():
  226. if not self.widgetTargetSrid.crs().isValid():
  227. QMessageBox.critical(self, self.tr("Import to Database"),
  228. self.tr("Invalid target srid: must be a valid crs."))
  229. return
  230. with OverrideCursor(Qt.WaitCursor):
  231. # store current input layer crs and encoding, so I can restore it
  232. prevInCrs = self.inLayer.crs()
  233. prevInEncoding = self.inLayer.dataProvider().encoding()
  234. try:
  235. schema = self.outUri.schema() if not self.cboSchema.isEnabled() else self.cboSchema.currentText()
  236. table = self.cboTable.currentText()
  237. # get pk and geom field names from the source layer or use the
  238. # ones defined by the user
  239. srcUri = QgsDataSourceUri(self.inLayer.source())
  240. pk = srcUri.keyColumn() if not self.chkPrimaryKey.isChecked() else self.editPrimaryKey.text()
  241. if not pk:
  242. pk = self.default_pk
  243. if self.inLayer.isSpatial() and self.chkGeomColumn.isEnabled():
  244. geom = srcUri.geometryColumn() if not self.chkGeomColumn.isChecked() else self.editGeomColumn.text()
  245. if not geom:
  246. geom = self.default_geom
  247. else:
  248. geom = None
  249. options = {}
  250. if self.chkLowercaseFieldNames.isEnabled() and self.chkLowercaseFieldNames.isChecked():
  251. pk = pk.lower()
  252. if geom:
  253. geom = geom.lower()
  254. options['lowercaseFieldNames'] = True
  255. # get output params, update output URI
  256. self.outUri.setDataSource(schema, table, geom, "", pk)
  257. typeName = self.db.dbplugin().typeName()
  258. providerName = self.db.dbplugin().providerName()
  259. if typeName == 'gpkg':
  260. uri = self.outUri.database()
  261. options['update'] = True
  262. options['driverName'] = 'GPKG'
  263. options['layerName'] = table
  264. else:
  265. uri = self.outUri.uri(False)
  266. if self.chkDropTable.isChecked():
  267. options['overwrite'] = True
  268. if self.chkSinglePart.isEnabled() and self.chkSinglePart.isChecked():
  269. options['forceSinglePartGeometryType'] = True
  270. outCrs = QgsCoordinateReferenceSystem()
  271. if self.chkTargetSrid.isEnabled() and self.chkTargetSrid.isChecked():
  272. outCrs = self.widgetTargetSrid.crs()
  273. # update input layer crs and encoding
  274. if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
  275. inCrs = self.widgetSourceSrid.crs()
  276. self.inLayer.setCrs(inCrs)
  277. if self.chkEncoding.isEnabled() and self.chkEncoding.isChecked() and self.cboEncoding.currentData() is None:
  278. enc = self.cboEncoding.currentText()
  279. self.inLayer.setProviderEncoding(enc)
  280. onlySelected = self.chkSelectedFeatures.isChecked()
  281. # do the import!
  282. ret, errMsg = QgsVectorLayerExporter.exportLayer(self.inLayer, uri, providerName, outCrs, onlySelected, options)
  283. except Exception as e:
  284. ret = -1
  285. errMsg = str(e)
  286. finally:
  287. # restore input layer crs and encoding
  288. self.inLayer.setCrs(prevInCrs)
  289. self.inLayer.setProviderEncoding(prevInEncoding)
  290. if ret != 0:
  291. output = QgsMessageViewer()
  292. output.setTitle(self.tr("Import to Database"))
  293. output.setMessageAsPlainText(self.tr("Error {0}\n{1}").format(ret, errMsg))
  294. output.showMessage()
  295. return
  296. # create spatial index
  297. if self.chkSpatialIndex.isEnabled() and self.chkSpatialIndex.isChecked():
  298. self.db.connector.createSpatialIndex((schema, table), geom)
  299. # add comment on table
  300. supportCom = self.db.supportsComment()
  301. if self.chkCom.isEnabled() and self.chkCom.isChecked() and supportCom:
  302. # using connector executing COMMENT ON TABLE query (with editCome.text() value)
  303. com = self.editCom.text()
  304. self.db.connector.commentTable(schema, table, com)
  305. self.db.connection().reconnect()
  306. self.db.refresh()
  307. QMessageBox.information(self, self.tr("Import to Database"), self.tr("Import was successful."))
  308. return QDialog.accept(self)
  309. def closeEvent(self, event):
  310. # destroy the input layer instance but only if it was created
  311. # from this dialog!
  312. self.deleteInputLayer()
  313. QDialog.closeEvent(self, event)