ResourceTree.py 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  1. # -*- coding: utf-8 -*-
  2. import ftplib
  3. import inspect
  4. import uuid
  5. import os
  6. import shutil
  7. import subprocess
  8. from PyQt5.QtWidgets import QMessageBox
  9. import psycopg2
  10. import redis
  11. from PyQt5 import QtCore, QtGui, QtWidgets
  12. from PyQt5.QtCore import QSize, QUrl, Qt, QTimer
  13. from PyQt5.QtGui import QIcon, QDrag
  14. from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QTreeWidget, QTreeWidgetItem, QMessageBox, \
  15. QInputDialog, QLineEdit, QWidget, QFileDialog, QMenu, QDialog, QFormLayout, QLabel, QTimeEdit, QDateTimeEdit
  16. # 导入QGIS相关模块
  17. from qgis.core import QgsProject, QgsVectorLayer, QgsRasterLayer
  18. from .TreeWidget import Tree
  19. from .FtpUitl import FtpOper
  20. from .FtpConfig import *
  21. from .PostgreSQL import PostgreSQL
  22. from qgis.utils import iface
  23. from qgis.core import QgsRectangle, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject, QgsVectorLayer
  24. from qgis import processing
  25. from .CombinedDialog import CombinedDialog
  26. # from .ModelWebView import ModelWebView
  27. current_directory = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # 获取当前路径
  28. class FormDialog(QDialog):
  29. def __init__(self, data):
  30. super().__init__()
  31. self.setWindowTitle("元数据信息")
  32. # 设置固定宽度和高度
  33. self.setFixedWidth(450) # 设置固定宽度为 400
  34. self.setFixedHeight(300) # 设置固定高度为 300
  35. # 创建表单布局
  36. self.layout = QFormLayout()
  37. # 创建输入框
  38. self.type_line_edit = QLineEdit(self)
  39. self.type_line_edit.setText(data[13])
  40. self.tablename_line_edit = QLineEdit(self)
  41. self.tablename_line_edit.setText(data[0])
  42. self.tablename_line_edit.setReadOnly(True)
  43. self.tablealias_line_edit = QLineEdit(self)
  44. self.tablealias_line_edit.setText(data[12])
  45. self.xzqfield_line_edit = QLineEdit(self)
  46. self.xzqfield_line_edit.setText(data[7])
  47. self.sjly_line_edit = QLineEdit(self)
  48. self.sjly_line_edit.setText(data[2])
  49. self.sjsx_line_edit = QLineEdit(self)
  50. self.sjsx_line_edit.setText(data[1])
  51. self.ywlx_line_edit = QLineEdit(self)
  52. self.ywlx_line_edit.setText(data[8])
  53. self.glbm_line_edit = QLineEdit(self)
  54. self.glbm_line_edit.setText(data[11])
  55. self.sjxzqh_line_edit = QLineEdit(self)
  56. self.sjxzqh_line_edit.setText(data[3])
  57. self.rksj_time_edit = QDateTimeEdit(self)
  58. self.rksj_time_edit.setDateTime(data[5])
  59. self.rksj_time_edit.setReadOnly(True)
  60. # 将控件添加到表单布局
  61. self.layout.addRow(QLabel("数据类型:"), self.type_line_edit)
  62. self.layout.addRow(QLabel("数据库表名:"), self.tablename_line_edit)
  63. self.layout.addRow(QLabel("表别名:"), self.tablealias_line_edit)
  64. self.layout.addRow(QLabel("行政区字段:"), self.xzqfield_line_edit)
  65. self.layout.addRow(QLabel("数据来源:"), self.sjly_line_edit)
  66. self.layout.addRow(QLabel("数据时效:"), self.sjsx_line_edit)
  67. self.layout.addRow(QLabel("业务类型:"), self.ywlx_line_edit)
  68. self.layout.addRow(QLabel("管理部门:"), self.glbm_line_edit)
  69. self.layout.addRow(QLabel("所属行政区:"), self.sjxzqh_line_edit)
  70. self.layout.addRow(QLabel("入库时间:"), self.rksj_time_edit)
  71. # 创建修改按钮
  72. # self.edit_button = QPushButton("修改", self)
  73. # self.edit_button.clicked.connect(self.on_edit)
  74. # self.layout.addWidget(self.edit_button)
  75. # 设置对话框的主布局
  76. self.setLayout(self.layout)
  77. def on_edit(self):
  78. # 获取输入的内容
  79. name = self.tablealias_line_edit.text()
  80. # 打印输入内容
  81. print(f"姓名: {name}")
  82. # 关闭对话框
  83. self.accept()
  84. class Ui_ResourceDockWidget(object):
  85. contexMenu = None
  86. # **修改1: 延迟初始化Redis连接**
  87. redis_client = None
  88. tableattr = None
  89. dbcoon = {
  90. "dbname": "real3d",
  91. "user": "postgres",
  92. "password": "postgis",
  93. "host": "192.168.60.2",
  94. "port": "5432",
  95. "schema": "base"
  96. }
  97. # **修改2: 添加数据库连接池标志**
  98. _db_connection = None
  99. _tree_initialized = False
  100. def setupUi(self, ResourceDockWidget):
  101. ResourceDockWidget.setObjectName("ResourceDockWidget")
  102. font = QtGui.QFont()
  103. font.setFamily("微软雅黑")
  104. ResourceDockWidget.setFont(font)
  105. self.dockWidgetContents = QtWidgets.QWidget()
  106. self.dockWidgetContents.setObjectName("resourceDockWidgetContents")
  107. # 添加FTP管理界面
  108. self.mainLayout = QVBoxLayout(self.dockWidgetContents)
  109. self.layoutH = QHBoxLayout() # 按钮布局
  110. self.layoutV = QVBoxLayout() # 树结构的布局
  111. # **修改3: 延迟初始化树结构**
  112. self.setupButtons()
  113. self.setupTreeWidget()
  114. ResourceDockWidget.setWidget(self.dockWidgetContents)
  115. self.retranslateUi(ResourceDockWidget)
  116. QtCore.QMetaObject.connectSlotsByName(ResourceDockWidget)
  117. self.setContextMenuPolicy(Qt.CustomContextMenu) # 右键菜单
  118. self.customContextMenuRequested.connect(self.rightClickMenu)
  119. # **修改4: 使用定时器延迟加载数据库数据**
  120. QTimer.singleShot(100, self.initializeTreeData)
  121. def setupButtons(self):
  122. """分离按钮初始化逻辑"""
  123. # 功能按钮区域
  124. # 加载到地图
  125. self.viewButton = QPushButton()
  126. self.viewButton.setCursor(Qt.PointingHandCursor)
  127. self.setBtnStyleSheet(self.viewButton)
  128. self.viewButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "map.png")))
  129. self.viewButton.setIconSize(QSize(20, 20))
  130. self.viewButton.setFixedSize(25, 25)
  131. self.viewButton.setToolTip("加载到地图")
  132. self.viewButton.clicked.connect(self.actionViewHandler)
  133. self.layoutH.addWidget(self.viewButton)
  134. # 数据统计
  135. self.staticButton = QPushButton()
  136. self.staticButton.setCursor(Qt.PointingHandCursor)
  137. self.setBtnStyleSheet(self.staticButton)
  138. self.staticButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "static.png")))
  139. self.staticButton.setIconSize(QSize(20, 20))
  140. self.staticButton.setFixedSize(25, 25)
  141. self.staticButton.setToolTip("数据统计")
  142. self.staticButton.clicked.connect(self.actionstaticHandler)
  143. self.layoutH.addWidget(self.staticButton)
  144. # 元数据
  145. self.resourceButton = QPushButton()
  146. self.resourceButton.setCursor(Qt.PointingHandCursor)
  147. self.setBtnStyleSheet(self.resourceButton)
  148. self.resourceButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "resource.png")))
  149. self.resourceButton.setIconSize(QSize(20, 20))
  150. self.resourceButton.setFixedSize(25, 25)
  151. self.resourceButton.setToolTip("元数据")
  152. self.resourceButton.clicked.connect(self.actionAttrHandler)
  153. self.layoutH.addWidget(self.resourceButton)
  154. # 导入矢量
  155. self.importButton = QPushButton()
  156. self.importButton.setCursor(Qt.PointingHandCursor)
  157. self.setBtnStyleSheet(self.importButton)
  158. self.importButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "importvector.png")))
  159. self.importButton.setIconSize(QSize(20, 20))
  160. self.importButton.setFixedSize(25, 25)
  161. self.importButton.setToolTip("导入矢量数据")
  162. self.importButton.clicked.connect(self.actionImportVectorHandler)
  163. self.layoutH.addWidget(self.importButton)
  164. # 导入栅格
  165. self.importRasterButton = QPushButton()
  166. self.importRasterButton.setCursor(Qt.PointingHandCursor)
  167. self.setBtnStyleSheet(self.importRasterButton)
  168. self.importRasterButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "importimage.png")))
  169. self.importRasterButton.setIconSize(QSize(20, 20))
  170. self.importRasterButton.setFixedSize(25, 25)
  171. self.importRasterButton.setToolTip("导入栅格数据")
  172. self.importRasterButton.clicked.connect(self.actionImportRasterHandler)
  173. self.layoutH.addWidget(self.importRasterButton)
  174. # 导出
  175. self.exportButton = QPushButton()
  176. self.exportButton.setCursor(Qt.PointingHandCursor)
  177. self.setBtnStyleSheet(self.exportButton)
  178. self.exportButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "export.png")))
  179. self.exportButton.setIconSize(QSize(20, 20))
  180. self.exportButton.setFixedSize(25, 25)
  181. self.exportButton.setToolTip("导出数据")
  182. self.exportButton.clicked.connect(self.actionExportHandler)
  183. self.layoutH.addWidget(self.exportButton)
  184. # 新建
  185. self.newButton = QPushButton()
  186. self.newButton.setCursor(Qt.PointingHandCursor)
  187. self.setBtnStyleSheet(self.newButton)
  188. self.newButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "new.png")))
  189. self.newButton.setIconSize(QSize(20, 20))
  190. self.newButton.setFixedSize(25, 25)
  191. self.newButton.setToolTip("新建节点")
  192. self.newButton.clicked.connect(self.actionNewNodeHandler)
  193. self.layoutH.addWidget(self.newButton)
  194. # 取消选中
  195. self.unSelectButton = QPushButton()
  196. self.unSelectButton.setCursor(Qt.PointingHandCursor)
  197. self.setBtnStyleSheet(self.unSelectButton)
  198. self.unSelectButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "unselect.png")))
  199. self.unSelectButton.setIconSize(QSize(20, 20))
  200. self.unSelectButton.setFixedSize(25, 25)
  201. self.unSelectButton.setToolTip("取消选中")
  202. self.unSelectButton.clicked.connect(self.actionUnselectHandler)
  203. self.layoutH.addWidget(self.unSelectButton)
  204. # 刷新树
  205. self.refreshButton = QPushButton()
  206. self.refreshButton.setCursor(Qt.PointingHandCursor)
  207. self.setBtnStyleSheet(self.refreshButton)
  208. self.refreshButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "refresh.png")))
  209. self.refreshButton.setIconSize(QSize(20, 20))
  210. self.refreshButton.setFixedSize(25, 25)
  211. self.refreshButton.setToolTip("刷新")
  212. self.refreshButton.clicked.connect(self.refreshTreeWidget)
  213. self.layoutH.addWidget(self.refreshButton)
  214. # 服务发布
  215. self.publishButton = QPushButton()
  216. self.publishButton.setCursor(Qt.PointingHandCursor)
  217. self.setBtnStyleSheet(self.publishButton)
  218. self.publishButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "publishserver.png")))
  219. self.publishButton.setIconSize(QSize(20, 20))
  220. self.publishButton.setFixedSize(25, 25)
  221. self.publishButton.setToolTip("刷新")
  222. self.publishButton.clicked.connect(self.publishserverHandler)
  223. self.layoutH.addWidget(self.publishButton)
  224. # 添加动态布局 元素自动左对齐
  225. self.layoutH.addStretch()
  226. self.mainLayout.addLayout(self.layoutH)
  227. def setupTreeWidget(self):
  228. """设置树形控件基本结构"""
  229. # **修改5: 先创建空的树形控件**
  230. self.treeWidget = QTreeWidget()
  231. self.treeWidget.setHeaderLabel("资源目录")
  232. # 启用拖动排序功能
  233. self.treeWidget.setDragEnabled(True)
  234. self.treeWidget.setAcceptDrops(True)
  235. self.treeWidget.setDragDropMode(QTreeWidget.InternalMove)
  236. self.treeWidget.setDefaultDropAction(Qt.MoveAction)
  237. self.layoutV.addWidget(self.treeWidget)
  238. self.treeWidget.itemSelectionChanged.connect(self.parametersHaveChanged)
  239. self.treeWidget.itemDoubleClicked.connect(self.on_item_double_clicked)
  240. self.treeWidget.viewport().setAcceptDrops(True)
  241. self.mainLayout.addLayout(self.layoutV)
  242. def initializeTreeData(self):
  243. """延迟初始化树形数据"""
  244. if not self._tree_initialized:
  245. try:
  246. # **修改6: 延迟初始化树形数据**
  247. tree = Tree()
  248. temp_tree = tree.initTreeWidget(self.dbcoon)
  249. # 将数据复制到已有的树形控件
  250. for i in range(temp_tree.topLevelItemCount()):
  251. item = temp_tree.topLevelItem(i).clone()
  252. self.treeWidget.addTopLevelItem(item)
  253. self._tree_initialized = True
  254. except Exception as e:
  255. print(f"初始化树形数据失败: {e}")
  256. # 添加一个错误提示项
  257. error_item = QTreeWidgetItem(self.treeWidget)
  258. error_item.setText(0, "数据加载失败,请点击刷新重试")
  259. def get_redis_client(self):
  260. """延迟初始化Redis连接"""
  261. if self.redis_client is None:
  262. try:
  263. self.redis_client = redis.Redis(host='192.168.60.2', port=6379, db=0, socket_timeout=5)
  264. # 测试连接
  265. self.redis_client.ping()
  266. except Exception as e:
  267. print(f"Redis连接失败: {e}")
  268. self.redis_client = None
  269. return self.redis_client
  270. def get_db_connection(self):
  271. """获取数据库连接,使用连接池思想"""
  272. if self._db_connection is None or self._db_connection.closed:
  273. try:
  274. dbcoon = self.dbcoon
  275. self._db_connection = PostgreSQL(
  276. schema=dbcoon["schema"],
  277. host=dbcoon["host"],
  278. port=dbcoon["port"],
  279. user=dbcoon["user"],
  280. password=dbcoon["password"],
  281. dbname=dbcoon["dbname"]
  282. )
  283. except Exception as e:
  284. print(f"数据库连接失败: {e}")
  285. return None
  286. return self._db_connection
  287. def refreshTreeWidget(self):
  288. # **修改7: 优化刷新逻辑**
  289. try:
  290. self.treeWidget.clear()
  291. self._tree_initialized = False
  292. self.initializeTreeData()
  293. except Exception as e:
  294. print(f"刷新树形控件失败: {e}")
  295. QMessageBox.warning(None, "错误", f"刷新失败: {str(e)}")
  296. # 设置按钮样式
  297. def setBtnStyleSheet(self, btn):
  298. btn.setStyleSheet("""
  299. QPushButton {
  300. border: 2px solid transparent; /* 默认边框透明 */
  301. padding: 10px;
  302. background-color: #f0f0f0;
  303. }
  304. QPushButton:hover {
  305. background-color: #f6f6f6;
  306. border: 1px solid #958585; /* 鼠标悬浮时显示蓝色边框 */
  307. }
  308. """)
  309. # 右键菜单
  310. def rightClickMenu(self, pos):
  311. try:
  312. self.currentItem = self.treeWidget.currentItem()
  313. self.contexMenu = QMenu()
  314. layertype = self.getNodeType(self.currentItem)
  315. if (layertype is not None and layertype != ""):
  316. if layertype == "table":
  317. self.actionView = self.contexMenu.addAction('打开属性表')
  318. self.actionView.triggered.connect(self.actionViewHandler)
  319. actionDelete = self.contexMenu.addAction('删除')
  320. actionDelete.triggered.connect(self.actionDeleteTableHandler)
  321. elif layertype == "osgb":
  322. self.actionView = self.contexMenu.addAction('加载三维数据')
  323. self.actionView.triggered.connect(self.actionViewHandler)
  324. actionDelete = self.contexMenu.addAction('删除')
  325. actionDelete.triggered.connect(self.actionDeleteTableHandler1)
  326. else:
  327. self.actionView = self.contexMenu.addAction('加载到地图')
  328. self.actionView.triggered.connect(self.actionViewHandler)
  329. actionExport = self.contexMenu.addAction('导出数据')
  330. actionExport.triggered.connect(self.actionExportHandler)
  331. self.actionAttr = self.contexMenu.addAction('元数据信息')
  332. self.actionAttr.triggered.connect(self.actionAttrHandler)
  333. if layertype == "vector":
  334. actionUpdate = self.contexMenu.addAction('数据更新')
  335. actionUpdate.triggered.connect(self.actionVectorUpdateHandler)
  336. actionRestore = self.contexMenu.addAction('版本回退')
  337. actionRestore.triggered.connect(self.actionVectorRestoreHandler)
  338. # self.actionAttributeTable = self.contexMenu.addAction('属性表')
  339. # self.actionAttributeTable.triggered.connect(self.actionAttrTableHandler)
  340. self.actionLayerAttr = self.contexMenu.addAction('图层属性')
  341. self.actionLayerAttr.triggered.connect(self.actionLayerAttrHandler)
  342. actionDelete = self.contexMenu.addAction('删除')
  343. actionDelete.triggered.connect(self.actionDeleteTableHandler)
  344. else:
  345. self.actionNewNode = self.contexMenu.addAction('新建资源目录')
  346. self.actionNewNode.triggered.connect(self.actionNewNodeHandler)
  347. self.actionImport = self.contexMenu.addAction('导入矢量数据')
  348. self.actionImport.triggered.connect(self.actionImportVectorHandler)
  349. self.actionImportRaster = self.contexMenu.addAction('导入栅格数据')
  350. self.actionImportRaster.triggered.connect(self.actionImportRasterHandler)
  351. rename = self.contexMenu.addAction('重命名')
  352. rename.triggered.connect(self.actionRenameNodeHandler)
  353. delete = self.contexMenu.addAction('删除')
  354. delete.triggered.connect(self.actionDeleteNodeHandler)
  355. self.contexMenu.exec_(self.mapToGlobal(pos))
  356. if self.contexMenu is not None:
  357. self.contexMenu.show()
  358. except Exception as e:
  359. print(e)
  360. def startDrag(self, supportedActions):
  361. # 创建一个drag item
  362. item = self.currentItem()
  363. print(item)
  364. if item:
  365. drag = QDrag(self)
  366. mimeData = self.model().mimeData([item])
  367. drag.setMimeData(mimeData)
  368. drag.exec_(Qt.MoveAction)
  369. def parametersHaveChanged(self):
  370. print("资源目录树节点选中变化了")
  371. def retranslateUi(self, SearchDockWidget):
  372. _translate = QtCore.QCoreApplication.translate
  373. SearchDockWidget.setWindowTitle(_translate("SearchDockWidget", "资源目录"))
  374. def actionUnselectHandler(self):
  375. self.treeWidget.clearSelection() # 清除所有选中的项
  376. self.treeWidget.setCurrentItem(None) # 清除当前选中的项
  377. def actionVectorUpdateHandler(self):
  378. self.deleteMenu()
  379. print("导入矢量数据操作,调用其他工具面板")
  380. self.currentItem = self.treeWidget.currentItem()
  381. params = {}
  382. if self.currentItem is not None:
  383. name = self.getNodeId(self.currentItem)
  384. # **修改8: 使用优化的数据库连接**
  385. pg = self.get_db_connection()
  386. if pg is None:
  387. QMessageBox.warning(None, "错误", "数据库连接失败")
  388. return
  389. rows = pg.getManagerTables()
  390. for i in range(len(rows)):
  391. row = rows[i]
  392. if name == row[1]:
  393. params = {"TARGET": i}
  394. break
  395. processing.execAlgorithmDialog("gdal:postgisupdate", params)
  396. def actionVectorRestoreHandler(self):
  397. self.deleteMenu()
  398. print("导入矢量数据操作,调用其他工具面板")
  399. self.currentItem = self.treeWidget.currentItem()
  400. params = {}
  401. if self.currentItem is not None:
  402. name = self.getNodeId(self.currentItem)
  403. # **修改9: 使用优化的数据库连接**
  404. pg = self.get_db_connection()
  405. if pg is None:
  406. QMessageBox.warning(None, "错误", "数据库连接失败")
  407. return
  408. rows = pg.getManagerTables()
  409. for i in range(len(rows)):
  410. row = rows[i]
  411. if name == row[1]:
  412. params = {"RESTORETABLE": i}
  413. break
  414. # **修改10: 使用优化的Redis连接**
  415. redis_client = self.get_redis_client()
  416. if redis_client:
  417. try:
  418. redis_client.set("curRestoreTable", name, 60 * 30)
  419. except Exception as e:
  420. print(f"Redis操作失败: {e}")
  421. processing.execAlgorithmDialog("gdal:postgisrestore", params)
  422. def actionImportVectorHandler(self):
  423. self.deleteMenu()
  424. print("导入矢量数据操作,调用其他工具面板")
  425. self.currentItem = self.treeWidget.currentItem()
  426. params = {}
  427. if self.currentItem is not None:
  428. name = self.getNodeName(self.currentItem)
  429. # **修改11: 使用优化的数据库连接**
  430. pg = self.get_db_connection()
  431. if pg is None:
  432. QMessageBox.warning(None, "错误", "数据库连接失败")
  433. return
  434. rows = pg.getVectorZyml()
  435. for i in range(len(rows)):
  436. row = rows[i]
  437. if name == row[1]:
  438. params = {"VECTOR_ZYML": i}
  439. break
  440. processing.execAlgorithmDialog("gdal:importvectorintopostgisdatabaseavailableconnections", params)
  441. def publishserverHandler(self):
  442. processing.execAlgorithmDialog("gdal:postgistogeoserver")
  443. def actionImportRasterHandler(self):
  444. self.deleteMenu()
  445. print("导入栅格数据操作,调用其他工具面板")
  446. self.currentItem = self.treeWidget.currentItem()
  447. params = {}
  448. if self.currentItem is not None:
  449. name = self.getNodeName(self.currentItem)
  450. # **修改12: 使用优化的数据库连接**
  451. pg = self.get_db_connection()
  452. if pg is None:
  453. QMessageBox.warning(None, "错误", "数据库连接失败")
  454. return
  455. rows = pg.getVectorZyml()
  456. for i in range(len(rows)):
  457. row = rows[i]
  458. if name == row[1]:
  459. params = {"VECTOR_ZYML": i}
  460. break
  461. processing.execAlgorithmDialog("gdal:importrasterintopostgisdatabase", params)
  462. def actionExportHandler(self):
  463. self.deleteMenu()
  464. print("导出数据操作,调用其他工具面板")
  465. self.currentItem = self.treeWidget.currentItem()
  466. if self.currentItem is not None:
  467. type = self.getNodeType(self.currentItem)
  468. self.actionViewHandler()
  469. if type == "vector":
  470. processing.execAlgorithmDialog("gdal:vectorexport")
  471. elif type == "raster":
  472. processing.execAlgorithmDialog("gdal:rasterexport")
  473. def deleteMenu(self):
  474. if self.contexMenu is not None:
  475. self.contexMenu.hide()
  476. self.contexMenu.close()
  477. self.contexMenu.clear()
  478. del self.contexMenu
  479. # self.contexMenu = None
  480. def actionDeleteTableHandler(self):
  481. print("")
  482. self.deleteMenu()
  483. self.currentItem = self.treeWidget.currentItem()
  484. id = self.getNodeId(self.currentItem)
  485. question = QMessageBox.question(None, '删除确认', '确认删除此项数据资源吗?')
  486. if question == QMessageBox.Yes:
  487. # **修改13: 使用优化的数据库连接**
  488. pg = self.get_db_connection()
  489. if pg is None:
  490. QMessageBox.warning(None, "错误", "数据库连接失败")
  491. return
  492. pg.dropTable(id)
  493. self.delete_node_and_children_with_widgets(self.currentItem)
  494. def actionDeleteTableHandler1(self):
  495. print("")
  496. self.deleteMenu()
  497. self.currentItem = self.treeWidget.currentItem()
  498. id = self.getNodeId(self.currentItem)
  499. question = QMessageBox.question(None, '删除确认', '确认删除此项数据资源吗?')
  500. if question == QMessageBox.Yes:
  501. # **修改14: 使用优化的数据库连接和错误处理**
  502. try:
  503. conn_params = {
  504. 'host': "192.168.60.2",
  505. 'port': "5432",
  506. 'dbname': "real3d",
  507. 'user': "postgres",
  508. 'password': "postgis"
  509. }
  510. conn = psycopg2.connect(**conn_params)
  511. cursor = conn.cursor()
  512. # 删除 t_vector_storage 中对应行(使用参数化查询)
  513. delete_sql = "DELETE FROM base.t_vector_storage WHERE name = %s"
  514. cursor.execute(delete_sql, (id,))
  515. conn.commit()
  516. cursor.close()
  517. conn.close()
  518. self.delete_node_and_children_with_widgets(self.currentItem)
  519. except Exception as e:
  520. QMessageBox.warning(None, "错误", f"删除操作失败: {str(e)}")
  521. def actionRenameNodeHandler(self):
  522. self.deleteMenu()
  523. self.currentItem = self.treeWidget.currentItem()
  524. id = self.getNodeId(self.currentItem)
  525. oldname = self.getNodeName(self.currentItem)
  526. newname, ok = QInputDialog.getText(QWidget(), '重命名', '请输入新名称', QLineEdit.Normal, oldname)
  527. if ok and newname != oldname:
  528. # **修改15: 使用优化的数据库连接**
  529. pg = self.get_db_connection()
  530. if pg is None:
  531. QMessageBox.warning(None, "错误", "数据库连接失败")
  532. return
  533. pg.renameZyml(id, newname)
  534. self.currentItem.setText(0, newname)
  535. def actionDeleteNodeHandler(self):
  536. self.deleteMenu()
  537. self.currentItem = self.treeWidget.currentItem()
  538. id = self.getNodeId(self.currentItem)
  539. # msgBox = QMessageBox()
  540. # # 自定义按钮
  541. # msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
  542. # msgBox.setButtonText(QMessageBox.Yes, "确定")
  543. # msgBox.setButtonText(QMessageBox.No, "取消")
  544. question = QMessageBox.question(None, '删除确认', '确认删除此项目录吗?')
  545. if question == QMessageBox.Yes:
  546. # **修改16: 使用优化的数据库连接**
  547. pg = self.get_db_connection()
  548. if pg is None:
  549. QMessageBox.warning(None, "错误", "数据库连接失败")
  550. return
  551. pg.deleteZyml(id)
  552. self.delete_node_and_children_with_widgets(self.currentItem)
  553. def delete_node_and_children_with_widgets(self, item):
  554. # 删除节点的所有子节点(递归)
  555. while item.childCount() > 0:
  556. child = item.child(0)
  557. self.delete_node_and_children_with_widgets(child) # 递归删除子节点
  558. # 移除小部件(如果有)
  559. self.treeWidget.removeItemWidget(item, 0) # 假设小部件在第0列
  560. if item.parent() is not None:
  561. item.parent().removeChild(item) # 删除该节点
  562. else:
  563. # 从树中删除该顶级节点
  564. index = self.treeWidget.indexOfTopLevelItem(item) # 获取该项的索引
  565. if index != -1:
  566. self.treeWidget.takeTopLevelItem(index) # 删除指定的顶级节点
  567. def actionNewNodeHandler(self):
  568. print("新建目录节点")
  569. self.deleteMenu()
  570. self.currentItem = self.treeWidget.currentItem()
  571. print(self.currentItem)
  572. name, ok = QInputDialog.getText(QWidget(), '资源目录名称', '请输入资源目录名称:')
  573. if ok:
  574. uid = self.getUUID()
  575. pid = ""
  576. if self.currentItem is not None:
  577. pid = self.getNodeId(self.currentItem)
  578. # **修改17: 使用优化的数据库连接**
  579. pg = self.get_db_connection()
  580. if pg is None:
  581. QMessageBox.warning(None, "错误", "数据库连接失败")
  582. return
  583. pg.addZyml(uid, pid, name)
  584. item = None
  585. if self.currentItem is not None:
  586. item = QTreeWidgetItem(self.currentItem)
  587. else:
  588. item = QTreeWidgetItem(self.treeWidget)
  589. item.setText(0, name)
  590. item.setText(1, uid)
  591. item.setText(2, "")
  592. def getNodeType(self, node):
  593. return node.text(2)
  594. def getUUID(self):
  595. id = uuid.uuid4().__str__()
  596. return id.replace("-", "")
  597. def getNodeName(self, node):
  598. return node.text(0)
  599. def getNodeId(self, node):
  600. return node.text(1)
  601. def actionAttrHandler(self):
  602. print("元数据信息")
  603. self.deleteMenu()
  604. if self.treeWidget.currentItem():
  605. self.currentItem = self.treeWidget.currentItem()
  606. print(self.currentItem)
  607. layertype = self.getNodeType(self.currentItem)
  608. print(layertype)
  609. if layertype is not None and layertype != "": # 选择的是数据项
  610. tablename = self.getNodeId(self.currentItem)
  611. # **修改18: 使用优化的数据库连接**
  612. # 获取元数据信息
  613. pg = self.get_db_connection()
  614. if pg is None:
  615. QMessageBox.warning(None, "错误", "数据库连接失败")
  616. return
  617. self.tableattr = pg.getResourceAttr(tablename)
  618. dia = FormDialog(self.tableattr)
  619. dia.exec_()
  620. else: # 选择的是目录
  621. QMessageBox.information(self, "提示信息", "请选择数据项")
  622. def on_item_double_clicked(self):
  623. self.actionViewHandler()
  624. # 属性表
  625. def actionAttrTableHandler(self):
  626. self.deleteMenu()
  627. if self.treeWidget.currentItem():
  628. self.currentItem = self.treeWidget.currentItem()
  629. layertype = self.getNodeType(self.currentItem)
  630. print(layertype)
  631. if layertype is not None and layertype != "": # 选择的是数据项
  632. tablename = self.getNodeId(self.currentItem)
  633. print(tablename)
  634. pglayer = None
  635. if layertype == "vector":
  636. pglayer = self.add_postgis_vector(tablename, True)
  637. elif layertype == "raster":
  638. pglayer = self.add_postgis_raster(tablename, True)
  639. # if pglayer is not None and pglayer.isValid() and pglayer.fields():
  640. # iface.showAttributeTable(pglayer) # Show the attribute table
  641. # else:
  642. # print("No valid layer selected or layer has no attributes.")
  643. if pglayer:
  644. dialog = CombinedDialog(pglayer)
  645. dialog.showDialog()
  646. else:
  647. print("No layer selected")
  648. # 图层属性
  649. def actionLayerAttrHandler(self):
  650. self.deleteMenu()
  651. if self.treeWidget.currentItem():
  652. self.currentItem = self.treeWidget.currentItem()
  653. layertype = self.getNodeType(self.currentItem)
  654. print(layertype)
  655. if layertype is not None and layertype != "": # 选择的是数据项
  656. tablename = self.getNodeId(self.currentItem)
  657. print(tablename)
  658. pglayer = None
  659. if layertype == "vector":
  660. pglayer = self.add_postgis_vector(tablename, True)
  661. elif layertype == "raster":
  662. pglayer = self.add_postgis_raster(tablename, True)
  663. # if pglayer is not None:
  664. # iface.showLayerProperties(pglayer)
  665. if pglayer:
  666. dialog = CombinedDialog(pglayer)
  667. dialog.showDialog()
  668. else:
  669. print("No layer selected")
  670. def actionViewHandler(self):
  671. print(self.contexMenu)
  672. self.deleteMenu()
  673. if self.treeWidget.currentItem():
  674. self.currentItem = self.treeWidget.currentItem()
  675. print(self.currentItem)
  676. layertype = self.getNodeType(self.currentItem)
  677. print(layertype)
  678. if layertype is not None and layertype != "": # 选择的是数据项
  679. tablename = self.getNodeId(self.currentItem)
  680. print(tablename)
  681. if layertype == "vector":
  682. self.add_postgis_vector(tablename, False)
  683. elif layertype == "raster":
  684. self.add_postgis_raster(tablename, False)
  685. elif layertype == "osgb":
  686. self.load_osgb_layer(tablename)
  687. elif layertype == "table":
  688. self.load_table_layer(tablename)
  689. def actionstaticHandler(self):
  690. # **修改19: 优化统计功能,添加异常处理和连接超时**
  691. import os
  692. import psycopg2
  693. import matplotlib.pyplot as plt
  694. from matplotlib import font_manager
  695. from PyQt5.QtWidgets import (
  696. QDialog, QVBoxLayout, QMessageBox, QPushButton,
  697. QFileDialog, QHBoxLayout, QLabel, QGridLayout
  698. )
  699. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  700. from PyQt5.QtCore import Qt
  701. from qgis.utils import iface
  702. def set_matplotlib_font():
  703. try:
  704. font_path = 'C:/Windows/Fonts/msyh.ttc'
  705. prop = font_manager.FontProperties(fname=font_path)
  706. plt.rcParams['font.family'] = prop.get_name()
  707. except Exception as e:
  708. print(f"字体设置失败:{e}")
  709. def get_xmlx_name_mapping(cursor):
  710. xmlx_name_map = {}
  711. cursor.execute("SELECT bsm, name, pbsm FROM base.t_vector_zyml")
  712. rows = cursor.fetchall()
  713. pbsm_map = {}
  714. for bsm, name, pbsm in rows:
  715. if bsm:
  716. xmlx_name_map[bsm] = name
  717. if pbsm:
  718. pbsm_map[bsm] = pbsm
  719. for bsm, pbsm in pbsm_map.items():
  720. if pbsm in xmlx_name_map:
  721. xmlx_name_map[bsm] = xmlx_name_map[pbsm]
  722. return xmlx_name_map
  723. def show_matplotlib_dialog(fig, total_size_mb, total_count, glbm_count, xmlx_count_unique):
  724. dialog = QDialog(iface.mainWindow())
  725. dialog.setWindowTitle("数据统计")
  726. layout = QVBoxLayout()
  727. top_bar = QHBoxLayout()
  728. export_btn = QPushButton("导出图表")
  729. export_btn.setFixedWidth(100)
  730. export_btn.setCursor(Qt.PointingHandCursor)
  731. def export_chart():
  732. save_path, _ = QFileDialog.getSaveFileName(
  733. dialog, "保存图表", "", "PNG 图片 (*.png);;PDF 文件 (*.pdf)"
  734. )
  735. if save_path:
  736. try:
  737. fig.savefig(save_path, dpi=300)
  738. QMessageBox.information(dialog, "成功", f"图表已导出到:\n{save_path}")
  739. except Exception as e:
  740. QMessageBox.critical(dialog, "导出失败", f"导出图表时出错:{str(e)}")
  741. export_btn.clicked.connect(export_chart)
  742. top_bar.addStretch()
  743. top_bar.addWidget(export_btn)
  744. layout.addLayout(top_bar)
  745. info_grid = QGridLayout()
  746. info_titles = ["总体数据量", "总体数据大小", "涉及部门", "数据分类"]
  747. info_values = [f"{total_count} 条", f"{total_size_mb:.2f} MB", f"{glbm_count} 个",
  748. f"{xmlx_count_unique} 个"]
  749. for i, (title, val) in enumerate(zip(info_titles, info_values)):
  750. label_title = QLabel(f"<b>{title}:</b>")
  751. label_value = QLabel(val)
  752. info_grid.addWidget(label_title, 0, i, alignment=Qt.AlignCenter)
  753. info_grid.addWidget(label_value, 1, i, alignment=Qt.AlignCenter)
  754. layout.addLayout(info_grid)
  755. canvas = FigureCanvas(fig)
  756. layout.addWidget(canvas)
  757. dialog.setLayout(layout)
  758. dialog.resize(1200, 900)
  759. dialog.exec_()
  760. try:
  761. # **修改20: 添加连接超时和错误处理**
  762. conn = psycopg2.connect(
  763. host='192.168.60.2', port='5432',
  764. dbname='real3d', user='postgres', password='postgis',
  765. connect_timeout=10) # 添加连接超时
  766. conn.autocommit = True
  767. cursor = conn.cursor()
  768. schema = 'base'
  769. table = 't_vector_storage'
  770. full_table = f'"{schema}"."{table}"'
  771. xmlx_name_map = get_xmlx_name_mapping(cursor)
  772. cursor.execute(f"SELECT xmlx FROM {full_table} WHERE xmlx IS NOT NULL")
  773. xmlx_values = cursor.fetchall()
  774. xmlx_count_map = {}
  775. for (x,) in xmlx_values:
  776. name = xmlx_name_map.get(x, str(x))
  777. xmlx_count_map[name] = xmlx_count_map.get(name, 0) + 1
  778. stat_results = {'xmlx': sorted(xmlx_count_map.items(), key=lambda x: x[1], reverse=True)}
  779. for field in ['sjlx', 'glbm']:
  780. cursor.execute(f"""
  781. SELECT {field}, COUNT(*) FROM {full_table}
  782. WHERE {field} IS NOT NULL
  783. GROUP BY {field}
  784. ORDER BY COUNT(*) DESC
  785. """)
  786. stat_results[field] = cursor.fetchall()
  787. cursor.execute(f"SELECT sjlx, name, sjywz, xmlx, glbm FROM {full_table}")
  788. rows = cursor.fetchall()
  789. size_map = {'vector': 0, 'raster': 0, 'table': 0, 'osgb': 0}
  790. size_map_by_field = {'xmlx': {}, 'sjlx': {}, 'glbm': {}}
  791. sjlx_ch_map = {'vector': '矢量', 'raster': '栅格', 'osgb': '三维', 'table': '附件'}
  792. for sjlx, name, sjywz, xmlx, glbm in rows:
  793. size = 0
  794. try:
  795. if sjlx == 'osgb' and sjywz and os.path.exists(sjywz):
  796. if os.path.isdir(sjywz):
  797. for dirpath, _, filenames in os.walk(sjywz):
  798. for fname in filenames:
  799. fpath = os.path.join(dirpath, fname)
  800. if os.path.exists(fpath):
  801. size += os.path.getsize(fpath)
  802. else:
  803. size = os.path.getsize(sjywz)
  804. elif sjlx in ['vector', 'raster']:
  805. if not name:
  806. continue
  807. for tname in [name, f'"{name}"', f'"{schema}"."{name}"']:
  808. try:
  809. cursor.execute("SELECT pg_total_relation_size(%s);", (tname,))
  810. res = cursor.fetchone()
  811. if res and res[0]:
  812. size = res[0]
  813. break
  814. except Exception:
  815. continue
  816. elif sjlx == 'table' and sjywz:
  817. for tname in [sjywz, f'"{sjywz}"', f'"{schema}"."{sjywz}"']:
  818. try:
  819. cursor.execute("SELECT pg_total_relation_size(%s);", (tname,))
  820. res = cursor.fetchone()
  821. if res and res[0]:
  822. size = res[0]
  823. break
  824. except Exception:
  825. continue
  826. except Exception:
  827. pass
  828. size_mb = size / 1024 / 1024
  829. size_map[sjlx] = size_map.get(sjlx, 0) + size_mb
  830. xmlx_name = xmlx_name_map.get(xmlx, str(xmlx)) if xmlx else "未知"
  831. size_map_by_field['xmlx'][xmlx_name] = size_map_by_field['xmlx'].get(xmlx_name, 0) + size_mb
  832. if sjlx:
  833. ch_sjlx = sjlx_ch_map.get(sjlx, sjlx)
  834. size_map_by_field['sjlx'][ch_sjlx] = size_map_by_field['sjlx'].get(ch_sjlx, 0) + size_mb
  835. if glbm:
  836. size_map_by_field['glbm'][glbm] = size_map_by_field['glbm'].get(glbm, 0) + size_mb
  837. total_size_mb = sum(size_map.values())
  838. cursor.execute(f"SELECT COUNT(*) FROM {full_table}")
  839. total_count = cursor.fetchone()[0]
  840. glbm_count = len(stat_results['glbm'])
  841. xmlx_count_unique = len(stat_results['xmlx'])
  842. set_matplotlib_font()
  843. fig, axes = plt.subplots(2, 3, figsize=(18, 10))
  844. field_names = {'xmlx': '数据分类', 'sjlx': '数据类型', 'glbm': '管理部门'}
  845. fields = ['xmlx', 'sjlx', 'glbm']
  846. for idx, field in enumerate(fields):
  847. data = stat_results.get(field, [])
  848. labels = [sjlx_ch_map.get(str(r[0]), str(r[0])) if field == 'sjlx' else str(r[0]) for r in data]
  849. sizes = [r[1] for r in data]
  850. ax = axes[0][idx]
  851. if not sizes or sum(sizes) == 0:
  852. ax.axis('off')
  853. ax.set_title(f"{field_names[field]}(无数据)")
  854. continue
  855. if len(labels) > 6:
  856. others_sum = sum(sizes[5:])
  857. labels = labels[:5] + ["其他"]
  858. sizes = sizes[:5] + [others_sum]
  859. wedges, texts, autotexts = ax.pie(
  860. sizes, labels=labels, autopct='%1.1f%%', startangle=90,
  861. wedgeprops=dict(width=0.4), textprops={'fontsize': 9})
  862. for i, autotext in enumerate(autotexts):
  863. autotext.set_text(f'{sizes[i]}')
  864. ax.axis('equal')
  865. ax.set_title(f"{field_names[field]}", fontsize=12)
  866. ax.legend(labels, loc='best', fontsize=8)
  867. for idx, field in enumerate(fields):
  868. ax = axes[1][idx]
  869. field_data = size_map_by_field[field]
  870. if not field_data:
  871. ax.axis('off')
  872. ax.set_title(f"{field_names[field]}(无数据)")
  873. continue
  874. sorted_items = sorted(field_data.items(), key=lambda x: x[1], reverse=True)
  875. if len(sorted_items) > 6:
  876. others = sum(v for _, v in sorted_items[5:])
  877. sorted_items = sorted_items[:5] + [("其他", others)]
  878. labels = [str(k) for k, _ in sorted_items]
  879. values = [round(v, 2) for _, v in sorted_items]
  880. bars = ax.bar(labels, values, color='steelblue', width=0.5, edgecolor='black')
  881. ax.set_title(f"{field_names[field]}", fontsize=12)
  882. ax.set_ylabel("大小 (MB)")
  883. ax.tick_params(axis='x', rotation=30)
  884. ax.set_ylim(0, max(values) * 1.2)
  885. for bar in bars:
  886. h = bar.get_height()
  887. ax.annotate(f'{h:.2f}', xy=(bar.get_x() + bar.get_width() / 2, h),
  888. xytext=(0, 3), textcoords="offset points",
  889. ha='center', va='bottom', fontsize=9)
  890. fig.suptitle("数据量与存储大小统计", fontsize=16)
  891. plt.tight_layout(rect=[0, 0.05, 1, 0.95])
  892. show_matplotlib_dialog(fig, total_size_mb, total_count, glbm_count, xmlx_count_unique)
  893. cursor.close()
  894. conn.close()
  895. except Exception as e:
  896. QMessageBox.critical(iface.mainWindow(), "错误", f"统计失败: {str(e)}")
  897. def load_table_layer(self, name: str, layer_name: str = None):
  898. """
  899. 根据 name(源文件名)从 t_vector_storage 获取 sjywz,加载 PostgreSQL 非空间表。
  900. """
  901. # **修改21: 优化数据库连接和错误处理**
  902. try:
  903. conn = psycopg2.connect(
  904. host="192.168.60.2",
  905. port="5432",
  906. dbname="real3d",
  907. user="postgres",
  908. password="postgis",
  909. connect_timeout=10
  910. )
  911. cursor = conn.cursor()
  912. # 按 name 字段查询(模糊匹配也可以加)
  913. cursor.execute(
  914. "SELECT sjywz FROM base.t_vector_storage WHERE name = %s ORDER BY rksj DESC LIMIT 1",
  915. (name,))
  916. result = cursor.fetchone()
  917. if not result:
  918. raise Exception(f"未找到 name 为 {name} 的记录。")
  919. sjywz = result[0]
  920. print(f"查到的 sjywz: {sjywz}")
  921. if '.' not in sjywz:
  922. raise Exception(f"sjywz 字段格式非法:{sjywz},应为 schema.tablename")
  923. schema, table = sjywz.split('.')
  924. if layer_name is None:
  925. layer_name = table
  926. uri = (
  927. f"dbname='real3d' host=192.168.60.2 port=5432 user='postgres' "
  928. f"password='postgis' sslmode=disable table=\"{schema}\".\"{table}\" sql="
  929. )
  930. layer = QgsVectorLayer(uri, layer_name, "postgres")
  931. if not layer.isValid():
  932. raise Exception(f"表格图层加载失败:{schema}.{table}")
  933. QgsProject.instance().addMapLayer(layer)
  934. iface.showAttributeTable(layer)
  935. cursor.close()
  936. conn.close()
  937. return layer
  938. except Exception as e:
  939. QMessageBox.warning(None, "错误", f"加载表格数据失败: {str(e)}")
  940. return None
  941. def load_osgb_layer(self, name):
  942. print(f"加载三维数据(OSGB): {name}")
  943. # **修改22: 优化OSGB数据加载,添加错误处理**
  944. try:
  945. # 连接到 PostgreSQL 数据库
  946. conn_params = {
  947. 'host': "192.168.60.2",
  948. 'port': "5432",
  949. 'dbname': "real3d",
  950. 'user': "postgres",
  951. 'password': "postgis",
  952. 'connect_timeout': 10
  953. }
  954. # 连接数据库
  955. conn = psycopg2.connect(**conn_params)
  956. cursor = conn.cursor()
  957. # 查询数据库获取路径
  958. cursor.execute("SELECT sjywz FROM base.t_vector_storage WHERE name = %s", (name,))
  959. result = cursor.fetchone()
  960. cursor.close()
  961. conn.close()
  962. if result is None:
  963. QMessageBox.warning(None, "错误", f"未找到三维数据路径: {name}")
  964. return
  965. osgb_dir = result[0] # 获取路径
  966. except Exception as e:
  967. QMessageBox.warning(None, "错误", f"数据库连接失败或查询错误: {str(e)}")
  968. return
  969. # 检查路径是否存在
  970. if not os.path.exists(osgb_dir):
  971. QMessageBox.warning(None, "错误", f"三维数据目录不存在: {osgb_dir}")
  972. return
  973. # 构造命令行命令来打开 OSGB 文件
  974. dasviewer_path = r"D:\app\DasViewer.exe"
  975. # **修改23: 检查DasViewer是否存在**
  976. if not os.path.exists(dasviewer_path):
  977. QMessageBox.warning(None, "错误", f"DasViewer程序不存在: {dasviewer_path}")
  978. return
  979. command = [dasviewer_path, osgb_dir]
  980. try:
  981. # 使用 subprocess 启动 DasViewer
  982. subprocess.run(command, check=True)
  983. QMessageBox.information(None, "提示", f"已启动 DasViewer 并加载三维数据: {osgb_dir}")
  984. except subprocess.CalledProcessError as e:
  985. QMessageBox.warning(None, "错误", f"打开 DasViewer 时出错: {str(e)}")
  986. except Exception as e:
  987. QMessageBox.warning(None, "错误", f"发生错误: {str(e)}")
  988. def add_local_vector_layer(self, layer_path, layer_name):
  989. # 添加矢量图层
  990. vector_layer = QgsVectorLayer(layer_path, layer_name, "ogr")
  991. # 将图层添加到项目中
  992. QgsProject.instance().addMapLayer(vector_layer)
  993. self.zoomToLayer(vector_layer)
  994. def add_postgis_vector(self, tablename, getlayer):
  995. # **修改24: 优化PostGIS矢量图层加载,添加错误处理**
  996. try:
  997. # 创建PostgreSQL图层
  998. pgtable = tablename.split(".")
  999. postgresLayer = QgsVectorLayer(
  1000. "dbname='{}' host={} port={} user='{}' password='{}' sslmode=disable checkPrimaryKeyUnicity='1' table=\"{}\".\"{}\" (geom)".format(
  1001. self.dbcoon["dbname"],
  1002. self.dbcoon["host"],
  1003. self.dbcoon["port"],
  1004. self.dbcoon["user"],
  1005. self.dbcoon["password"],
  1006. pgtable[0],
  1007. pgtable[1]
  1008. ),
  1009. pgtable[1], 'postgres')
  1010. if not postgresLayer.isValid():
  1011. QMessageBox.warning(None, "错误", f"无法加载矢量图层: {tablename}")
  1012. return None
  1013. if getlayer == True:
  1014. return postgresLayer
  1015. # 添加图层到QGIS项目
  1016. QgsProject.instance().addMapLayer(postgresLayer)
  1017. self.zoomToLayer(postgresLayer)
  1018. return postgresLayer
  1019. except Exception as e:
  1020. QMessageBox.warning(None, "错误", f"加载矢量图层失败: {str(e)}")
  1021. return None
  1022. def add_postgis_raster(self, tablename, getlayer):
  1023. # **修改25: 优化PostGIS栅格图层加载,添加错误处理**
  1024. try:
  1025. # 创建PostgreSQL图层
  1026. pgtable = tablename.split(".")
  1027. postgresLayer = QgsRasterLayer(
  1028. "dbname='{}' host={} port={} user='{}' password='{}' sslmode=disable checkPrimaryKeyUnicity='1' table=\"{}\".\"{}\" (rast)".format(
  1029. self.dbcoon["dbname"],
  1030. self.dbcoon["host"],
  1031. self.dbcoon["port"],
  1032. self.dbcoon["user"],
  1033. self.dbcoon["password"],
  1034. pgtable[0],
  1035. pgtable[1]
  1036. ),
  1037. pgtable[1], "postgresraster")
  1038. if not postgresLayer.isValid():
  1039. QMessageBox.warning(None, "错误", f"无法加载栅格图层: {tablename}")
  1040. return None
  1041. if getlayer == True:
  1042. return postgresLayer
  1043. # 添加图层到QGIS项目
  1044. QgsProject.instance().addMapLayer(postgresLayer)
  1045. self.zoomToLayer(postgresLayer)
  1046. return postgresLayer
  1047. except Exception as e:
  1048. QMessageBox.warning(None, "错误", f"加载栅格图层失败: {str(e)}")
  1049. return None
  1050. def add_local_raster_layer(self, layer_path, layer_name):
  1051. # 添加栅格图层
  1052. raster_layer = QgsRasterLayer(layer_path, layer_name)
  1053. # 将图层添加到项目中
  1054. QgsProject.instance().addMapLayer(raster_layer)
  1055. self.zoomToLayer(raster_layer)
  1056. def zoomToLayer(self, layer):
  1057. # 当前活动图层
  1058. activelayer = iface.activeLayer()
  1059. # 缩放到图层范围
  1060. # 获取当前项目的 CRS
  1061. current_canvas_crs = QgsProject.instance().crs()
  1062. current_layer_crs = layer.crs()
  1063. if activelayer is None or activelayer == layer or current_canvas_crs.authid() == current_layer_crs.authid():
  1064. iface.mapCanvas().setExtent(layer.extent())
  1065. else:
  1066. print(current_canvas_crs.authid())
  1067. print(current_layer_crs.authid())
  1068. rect = layer.extent()
  1069. print(rect)
  1070. # 创建坐标转换器
  1071. transform = QgsCoordinateTransform(current_layer_crs, current_canvas_crs, QgsProject.instance())
  1072. print(current_layer_crs)
  1073. print(current_layer_crs)
  1074. # 使用坐标转换器将 QgsRectangle 转换为目标 CRS
  1075. # 我们需要将矩形的四个角进行转换
  1076. rect_min_x, rect_min_y = transform.transform(rect.xMinimum(), rect.yMinimum())
  1077. rect_max_x, rect_max_y = transform.transform(rect.xMaximum(), rect.yMaximum())
  1078. # 创建新的转换后的
  1079. transformed_rect = QgsRectangle(rect_min_x, rect_min_y, rect_max_x, rect_max_y)
  1080. iface.mapCanvas().setExtent(transformed_rect)
  1081. iface.mapCanvas().refresh()