ResourceTree.py 83 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951
  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 types import MethodType
  12. from PyQt5 import QtCore, QtGui, QtWidgets
  13. from PyQt5.QtCore import QSize, QUrl, Qt, QTimer
  14. from PyQt5.QtGui import QIcon, QDrag
  15. from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QTreeWidget, QTreeWidgetItem, QMessageBox, \
  16. QInputDialog, QLineEdit, QWidget, QFileDialog, QMenu, QDialog, QFormLayout, QLabel, QTimeEdit, QDateTimeEdit
  17. # 导入QGIS相关模块
  18. from qgis.core import QgsProject, QgsVectorLayer, QgsRasterLayer, QgsApplication
  19. from .TreeWidget import Tree
  20. from .FtpUitl import FtpOper
  21. from .FtpConfig import *
  22. from .PostgreSQL import PostgreSQL
  23. from qgis.utils import iface
  24. from qgis.core import QgsRectangle, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject, QgsVectorLayer
  25. from qgis import processing
  26. from .CombinedDialog import CombinedDialog
  27. from PyQt5.QtWidgets import QAbstractItemView
  28. # from .ModelWebView import ModelWebView
  29. current_directory = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # 获取当前路径
  30. class FormDialog(QDialog):
  31. def __init__(self, data):
  32. super().__init__()
  33. self.setWindowTitle("元数据信息")
  34. # 设置固定宽度和高度
  35. self.setFixedWidth(450) # 设置固定宽度为 400
  36. self.setFixedHeight(300) # 设置固定高度为 300
  37. # 创建表单布局
  38. self.layout = QFormLayout()
  39. # 创建输入框
  40. self.type_line_edit = QLineEdit(self)
  41. self.type_line_edit.setText(data[13])
  42. self.tablename_line_edit = QLineEdit(self)
  43. self.tablename_line_edit.setText(data[0])
  44. self.tablename_line_edit.setReadOnly(True)
  45. self.tablealias_line_edit = QLineEdit(self)
  46. self.tablealias_line_edit.setText(data[12])
  47. self.xzqfield_line_edit = QLineEdit(self)
  48. self.xzqfield_line_edit.setText(data[7])
  49. self.sjly_line_edit = QLineEdit(self)
  50. self.sjly_line_edit.setText(data[2])
  51. self.sjsx_line_edit = QLineEdit(self)
  52. self.sjsx_line_edit.setText(data[1])
  53. self.ywlx_line_edit = QLineEdit(self)
  54. self.ywlx_line_edit.setText(data[8])
  55. self.glbm_line_edit = QLineEdit(self)
  56. self.glbm_line_edit.setText(data[11])
  57. self.sjxzqh_line_edit = QLineEdit(self)
  58. self.sjxzqh_line_edit.setText(data[3])
  59. self.rksj_time_edit = QDateTimeEdit(self)
  60. self.rksj_time_edit.setDateTime(data[5])
  61. self.rksj_time_edit.setReadOnly(True)
  62. # 将控件添加到表单布局
  63. self.layout.addRow(QLabel("数据类型:"), self.type_line_edit)
  64. self.layout.addRow(QLabel("数据库表名:"), self.tablename_line_edit)
  65. self.layout.addRow(QLabel("表别名:"), self.tablealias_line_edit)
  66. self.layout.addRow(QLabel("行政区字段:"), self.xzqfield_line_edit)
  67. self.layout.addRow(QLabel("数据来源:"), self.sjly_line_edit)
  68. self.layout.addRow(QLabel("数据时效:"), self.sjsx_line_edit)
  69. self.layout.addRow(QLabel("业务类型:"), self.ywlx_line_edit)
  70. self.layout.addRow(QLabel("管理部门:"), self.glbm_line_edit)
  71. self.layout.addRow(QLabel("所属行政区:"), self.sjxzqh_line_edit)
  72. self.layout.addRow(QLabel("入库时间:"), self.rksj_time_edit)
  73. # 创建修改按钮
  74. # self.edit_button = QPushButton("修改", self)
  75. # self.edit_button.clicked.connect(self.on_edit)
  76. # self.layout.addWidget(self.edit_button)
  77. # 设置对话框的主布局
  78. self.setLayout(self.layout)
  79. def on_edit(self):
  80. # 获取输入的内容
  81. name = self.tablealias_line_edit.text()
  82. # 打印输入内容
  83. print(f"姓名: {name}")
  84. # 关闭对话框
  85. self.accept()
  86. class Ui_ResourceDockWidget(object):
  87. contexMenu = None
  88. # **修改1: 延迟初始化Redis连接**
  89. redis_client = None
  90. tableattr = None
  91. dbcoon = {
  92. "dbname": "datamanager",
  93. "user": "postgres",
  94. "password": "postgis",
  95. "host": "192.168.60.2",
  96. "port": "5432",
  97. "schema": "base"
  98. }
  99. # **修改2: 添加数据库连接池标志**
  100. _db_connection = None
  101. _tree_initialized = False
  102. def setupUi(self, ResourceDockWidget):
  103. ResourceDockWidget.setObjectName("ResourceDockWidget")
  104. font = QtGui.QFont()
  105. font.setFamily("微软雅黑")
  106. ResourceDockWidget.setFont(font)
  107. self.dockWidgetContents = QtWidgets.QWidget()
  108. self.dockWidgetContents.setObjectName("resourceDockWidgetContents")
  109. # 添加FTP管理界面
  110. self.mainLayout = QVBoxLayout(self.dockWidgetContents)
  111. self.layoutH = QHBoxLayout() # 按钮布局
  112. self.layoutV = QVBoxLayout() # 树结构的布局
  113. # **修改3: 延迟初始化树结构**
  114. self.setupButtons()
  115. self.setupTreeWidget()
  116. self.setup_tree_drag_drop()
  117. ResourceDockWidget.setWidget(self.dockWidgetContents)
  118. self.retranslateUi(ResourceDockWidget)
  119. QtCore.QMetaObject.connectSlotsByName(ResourceDockWidget)
  120. self.setContextMenuPolicy(Qt.CustomContextMenu) # 右键菜单
  121. self.customContextMenuRequested.connect(self.rightClickMenu)
  122. # **修改4: 使用定时器延迟加载数据库数据**
  123. QTimer.singleShot(100, self.initializeTreeData)
  124. def setupButtons(self):
  125. """分离按钮初始化逻辑"""
  126. # 功能按钮区域
  127. # 加载到地图
  128. self.viewButton = QPushButton()
  129. self.viewButton.setCursor(Qt.PointingHandCursor)
  130. self.setBtnStyleSheet(self.viewButton)
  131. self.viewButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "map.png")))
  132. self.viewButton.setIconSize(QSize(20, 20))
  133. self.viewButton.setFixedSize(25, 25)
  134. self.viewButton.setToolTip("加载到地图")
  135. self.viewButton.clicked.connect(self.actionViewHandler)
  136. self.layoutH.addWidget(self.viewButton)
  137. # 数据统计
  138. self.staticButton = QPushButton()
  139. self.staticButton.setCursor(Qt.PointingHandCursor)
  140. self.setBtnStyleSheet(self.staticButton)
  141. self.staticButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "static.png")))
  142. self.staticButton.setIconSize(QSize(20, 20))
  143. self.staticButton.setFixedSize(25, 25)
  144. self.staticButton.setToolTip("数据统计")
  145. self.staticButton.clicked.connect(self.actionstaticHandler)
  146. self.layoutH.addWidget(self.staticButton)
  147. # 元数据
  148. self.resourceButton = QPushButton()
  149. self.resourceButton.setCursor(Qt.PointingHandCursor)
  150. self.setBtnStyleSheet(self.resourceButton)
  151. self.resourceButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "resource.png")))
  152. self.resourceButton.setIconSize(QSize(20, 20))
  153. self.resourceButton.setFixedSize(25, 25)
  154. self.resourceButton.setToolTip("元数据")
  155. self.resourceButton.clicked.connect(self.actionAttrHandler)
  156. self.layoutH.addWidget(self.resourceButton)
  157. # 导入矢量
  158. self.importButton = QPushButton()
  159. self.importButton.setCursor(Qt.PointingHandCursor)
  160. self.setBtnStyleSheet(self.importButton)
  161. self.importButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "importvector.png")))
  162. self.importButton.setIconSize(QSize(20, 20))
  163. self.importButton.setFixedSize(25, 25)
  164. self.importButton.setToolTip("导入矢量数据")
  165. self.importButton.clicked.connect(self.actionImportVectorHandler)
  166. self.layoutH.addWidget(self.importButton)
  167. # 导入栅格
  168. self.importRasterButton = QPushButton()
  169. self.importRasterButton.setCursor(Qt.PointingHandCursor)
  170. self.setBtnStyleSheet(self.importRasterButton)
  171. self.importRasterButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "importimage.png")))
  172. self.importRasterButton.setIconSize(QSize(20, 20))
  173. self.importRasterButton.setFixedSize(25, 25)
  174. self.importRasterButton.setToolTip("导入栅格数据")
  175. self.importRasterButton.clicked.connect(self.actionImportRasterHandler)
  176. self.layoutH.addWidget(self.importRasterButton)
  177. # 导出
  178. self.exportButton = QPushButton()
  179. self.exportButton.setCursor(Qt.PointingHandCursor)
  180. self.setBtnStyleSheet(self.exportButton)
  181. self.exportButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "export.png")))
  182. self.exportButton.setIconSize(QSize(20, 20))
  183. self.exportButton.setFixedSize(25, 25)
  184. self.exportButton.setToolTip("导出数据")
  185. self.exportButton.clicked.connect(self.actionExportHandler)
  186. self.layoutH.addWidget(self.exportButton)
  187. # 新建
  188. self.newButton = QPushButton()
  189. self.newButton.setCursor(Qt.PointingHandCursor)
  190. self.setBtnStyleSheet(self.newButton)
  191. self.newButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "new.png")))
  192. self.newButton.setIconSize(QSize(20, 20))
  193. self.newButton.setFixedSize(25, 25)
  194. self.newButton.setToolTip("新建节点")
  195. self.newButton.clicked.connect(self.actionNewNodeHandler)
  196. self.layoutH.addWidget(self.newButton)
  197. # 取消选中
  198. self.unSelectButton = QPushButton()
  199. self.unSelectButton.setCursor(Qt.PointingHandCursor)
  200. self.setBtnStyleSheet(self.unSelectButton)
  201. self.unSelectButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "unselect.png")))
  202. self.unSelectButton.setIconSize(QSize(20, 20))
  203. self.unSelectButton.setFixedSize(25, 25)
  204. self.unSelectButton.setToolTip("取消选中")
  205. self.unSelectButton.clicked.connect(self.actionUnselectHandler)
  206. self.layoutH.addWidget(self.unSelectButton)
  207. # 刷新树
  208. self.refreshButton = QPushButton()
  209. self.refreshButton.setCursor(Qt.PointingHandCursor)
  210. self.setBtnStyleSheet(self.refreshButton)
  211. self.refreshButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "refresh.png")))
  212. self.refreshButton.setIconSize(QSize(20, 20))
  213. self.refreshButton.setFixedSize(25, 25)
  214. self.refreshButton.setToolTip("刷新")
  215. self.refreshButton.clicked.connect(self.refreshTreeWidget)
  216. self.layoutH.addWidget(self.refreshButton)
  217. # 服务发布
  218. self.publishButton = QPushButton()
  219. self.publishButton.setCursor(Qt.PointingHandCursor)
  220. self.setBtnStyleSheet(self.publishButton)
  221. self.publishButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "publishserver.png")))
  222. self.publishButton.setIconSize(QSize(20, 20))
  223. self.publishButton.setFixedSize(25, 25)
  224. self.publishButton.setToolTip("服务发布")
  225. self.publishButton.clicked.connect(self.publishserverHandler)
  226. self.layoutH.addWidget(self.publishButton)
  227. # 添加动态布局 元素自动左对齐
  228. self.layoutH.addStretch()
  229. self.mainLayout.addLayout(self.layoutH)
  230. def setupTreeWidget(self):
  231. self.treeWidget = QTreeWidget()
  232. self.treeWidget.setHeaderLabel("资源目录")
  233. self.treeWidget.setDragEnabled(True)
  234. self.treeWidget.setAcceptDrops(True)
  235. self.treeWidget.setDragDropMode(QTreeWidget.InternalMove)
  236. self.treeWidget.setDefaultDropAction(Qt.MoveAction)
  237. self.treeWidget.setDropIndicatorShown(True)
  238. self.treeWidget.model().rowsMoved.connect(self.on_tree_item_dropped) # 拖动后保存排序
  239. self.layoutV.addWidget(self.treeWidget)
  240. self.treeWidget.itemSelectionChanged.connect(self.parametersHaveChanged)
  241. self.treeWidget.itemDoubleClicked.connect(self.on_item_double_clicked)
  242. self.treeWidget.viewport().setAcceptDrops(True)
  243. self.mainLayout.addLayout(self.layoutV)
  244. def get_redis_client(self):
  245. """延迟初始化Redis连接"""
  246. if self.redis_client is None:
  247. try:
  248. self.redis_client = redis.Redis(host='192.168.60.2', port=6379, db=0, socket_timeout=5)
  249. # 测试连接
  250. self.redis_client.ping()
  251. except Exception as e:
  252. print(f"Redis连接失败: {e}")
  253. self.redis_client = None
  254. return self.redis_client
  255. def get_db_connection(self):
  256. """获取数据库连接,使用连接池思想"""
  257. if self._db_connection is None or self._db_connection.close:
  258. try:
  259. dbcoon = self.dbcoon
  260. self._db_connection = PostgreSQL(
  261. schema=dbcoon["schema"],
  262. host=dbcoon["host"],
  263. port=dbcoon["port"],
  264. user=dbcoon["user"],
  265. password=dbcoon["password"],
  266. dbname=dbcoon["dbname"]
  267. )
  268. except Exception as e:
  269. print(f"数据库连接失败: {e}")
  270. return None
  271. return self._db_connection
  272. # 设置按钮样式
  273. def setBtnStyleSheet(self, btn):
  274. btn.setStyleSheet("""
  275. QPushButton {
  276. border: 2px solid transparent; /* 默认边框透明 */
  277. padding: 10px;
  278. background-color: #f0f0f0;
  279. }
  280. QPushButton:hover {
  281. background-color: #f6f6f6;
  282. border: 1px solid #958585; /* 鼠标悬浮时显示蓝色边框 */
  283. }
  284. """)
  285. # 右键菜单
  286. def rightClickMenu(self, pos):
  287. try:
  288. self.currentItem = self.treeWidget.currentItem()
  289. self.contexMenu = QMenu()
  290. layertype = self.getNodeType(self.currentItem)
  291. if (layertype is not None and layertype != ""):
  292. if layertype == "table":
  293. self.actionView = self.contexMenu.addAction('打开属性表')
  294. self.actionView.triggered.connect(self.actionViewHandler)
  295. actionDelete = self.contexMenu.addAction('删除')
  296. actionDelete.triggered.connect(self.actionDeleteTableHandler)
  297. elif layertype == "osgb":
  298. self.actionView = self.contexMenu.addAction('加载三维数据')
  299. self.actionView.triggered.connect(self.actionViewHandler)
  300. actionDelete = self.contexMenu.addAction('删除')
  301. actionDelete.triggered.connect(self.actionDeleteTableHandler1)
  302. else:
  303. self.actionView = self.contexMenu.addAction('加载到地图')
  304. self.actionView.triggered.connect(self.actionViewHandler)
  305. actionExport = self.contexMenu.addAction('导出数据')
  306. actionExport.triggered.connect(self.actionExportHandler)
  307. self.actionAttr = self.contexMenu.addAction('元数据信息')
  308. self.actionAttr.triggered.connect(self.actionAttrHandler)
  309. if layertype == "vector":
  310. actionUpdate = self.contexMenu.addAction('数据更新')
  311. actionUpdate.triggered.connect(self.actionVectorUpdateHandler)
  312. actionRestore = self.contexMenu.addAction('版本回退')
  313. actionRestore.triggered.connect(self.actionVectorRestoreHandler)
  314. # self.actionAttributeTable = self.contexMenu.addAction('属性表')
  315. # self.actionAttributeTable.triggered.connect(self.actionAttrTableHandler)
  316. self.actionLayerAttr = self.contexMenu.addAction('图层属性')
  317. self.actionLayerAttr.triggered.connect(self.actionLayerAttrHandler)
  318. rename = self.contexMenu.addAction('重命名')
  319. rename.triggered.connect(self.actionRenameDataSource)
  320. actionDelete = self.contexMenu.addAction('删除')
  321. actionDelete.triggered.connect(self.actionDeleteTableHandler1)
  322. else:
  323. self.actionNewNode = self.contexMenu.addAction('新建资源目录')
  324. self.actionNewNode.triggered.connect(self.actionNewNodeHandler)
  325. self.actionImport = self.contexMenu.addAction('导入矢量数据')
  326. self.actionImport.triggered.connect(self.actionImportVectorHandler)
  327. self.actionImportRaster = self.contexMenu.addAction('导入栅格数据')
  328. self.actionImportRaster.triggered.connect(self.actionImportRasterHandler)
  329. rename = self.contexMenu.addAction('重命名')
  330. rename.triggered.connect(self.actionRenameNodeHandler)
  331. delete = self.contexMenu.addAction('删除')
  332. delete.triggered.connect(self.actionDeleteNodeHandler)
  333. self.contexMenu.exec_(self.mapToGlobal(pos))
  334. if self.contexMenu is not None:
  335. self.contexMenu.show()
  336. except Exception as e:
  337. print(e)
  338. def startDrag(self, supportedActions):
  339. # 创建一个drag item
  340. item = self.currentItem()
  341. print(item)
  342. if item:
  343. drag = QDrag(self)
  344. mimeData = self.model().mimeData([item])
  345. drag.setMimeData(mimeData)
  346. drag.exec_(Qt.MoveAction)
  347. def parametersHaveChanged(self):
  348. print("资源目录树节点选中变化了")
  349. def retranslateUi(self, SearchDockWidget):
  350. _translate = QtCore.QCoreApplication.translate
  351. SearchDockWidget.setWindowTitle(_translate("SearchDockWidget", "资源目录"))
  352. def actionUnselectHandler(self):
  353. self.treeWidget.clearSelection() # 清除所有选中的项
  354. self.treeWidget.setCurrentItem(None) # 清除当前选中的项
  355. def actionVectorUpdateHandler(self):
  356. self.deleteMenu()
  357. print("数据更新操作,调用其他工具面板")
  358. # 检查是否有选中的项目
  359. self.currentItem = self.treeWidget.currentItem()
  360. if self.currentItem is None:
  361. print("没有选中任何项目")
  362. QMessageBox.warning(None, "提示", "请先选择一个数据项目")
  363. return
  364. # 获取项目信息
  365. name = self.getNodeId(self.currentItem)
  366. type = self.getNodeType(self.currentItem)
  367. print(f"选中项目类型: {type}")
  368. print(f"选中项目名称: {name}")
  369. print(f"选中项目ID: {name}")
  370. print(f"选中项目对象: {self.currentItem}")
  371. # 检查项目类型
  372. if type != "vector":
  373. print(f"不支持的数据类型: {type}")
  374. QMessageBox.warning(None, "提示", f"数据更新功能只支持矢量数据,当前类型: {type}")
  375. return
  376. params = {}
  377. try:
  378. # 使用优化的数据库连接
  379. pg = self.get_db_connection()
  380. if pg is None:
  381. print("数据库连接失败")
  382. QMessageBox.warning(None, "错误", "数据库连接失败")
  383. return
  384. rows = pg.getManagerTables()
  385. print(f"获取到管理表数据: {len(rows)} 行")
  386. for i in range(len(rows)):
  387. row = rows[i]
  388. if name == row[1]:
  389. params = {"TARGET": i}
  390. print(f"找到匹配项,参数: {params}")
  391. break
  392. if not params:
  393. print("未找到匹配的数据项")
  394. QMessageBox.warning(None, "提示", "未找到匹配的数据项")
  395. return
  396. except Exception as e:
  397. print(f"获取数据失败: {e}")
  398. QMessageBox.warning(None, "错误", f"获取数据失败: {str(e)}")
  399. return
  400. print(f"最终参数: {params}")
  401. # 使用QTimer延迟调用更新算法,确保UI线程准备就绪
  402. def delayed_update():
  403. try:
  404. print("调用数据更新算法")
  405. # 检查算法是否存在
  406. registry = QgsApplication.processingRegistry()
  407. # 尝试多个可能的数据更新算法
  408. algorithms_to_try = [
  409. "gdal:Fpostgisupdate",
  410. "gdal:postgisrestore", # 作为备选
  411. "native:savefeatures", # 保存要素
  412. "gdal:vectortoshapefile" # 矢量转shapefile
  413. ]
  414. success = False
  415. for alg_name in algorithms_to_try:
  416. try:
  417. print(f"尝试算法: {alg_name}")
  418. alg = registry.algorithmById(alg_name)
  419. if alg is None:
  420. print(f"算法 {alg_name} 不存在")
  421. continue
  422. print(f"算法 {alg_name} 存在,正在打开对话框...")
  423. if alg_name == "gdal:postgisrestore":
  424. # 对于restore算法,使用不同的参数
  425. restore_params = {"RESTORETABLE": params.get("TARGET", 0)}
  426. processing.execAlgorithmDialog(alg_name, restore_params)
  427. else:
  428. processing.execAlgorithmDialog(alg_name, params)
  429. print(f"算法 {alg_name} 对话框已打开")
  430. success = True
  431. break
  432. except Exception as alg_e:
  433. print(f"算法 {alg_name} 调用失败: {alg_e}")
  434. continue
  435. if not success:
  436. print("所有数据更新算法都调用失败")
  437. QMessageBox.warning(None, "错误", "无法找到可用的数据更新算法")
  438. except Exception as e:
  439. print(f"数据更新算法调用失败: {e}")
  440. QMessageBox.warning(None, "错误", f"数据更新操作失败: {str(e)}")
  441. # 使用QTimer延迟执行,确保UI线程准备就绪
  442. QTimer.singleShot(100, delayed_update)
  443. def actionVectorRestoreHandler(self) -> None:
  444. """处理版本回退操作
  445. 此函数负责调用QGIS处理算法来回退PostGIS中的矢量数据版本。
  446. Returns:
  447. None
  448. """
  449. self.deleteMenu()
  450. print("版本回退操作,调用其他工具面板")
  451. # 检查是否有选中的项目
  452. self.currentItem = self.treeWidget.currentItem()
  453. if self.currentItem is None:
  454. QMessageBox.warning(None, "提示", "请先选择一个数据项目")
  455. return
  456. # 获取项目类型
  457. type = self.getNodeType(self.currentItem)
  458. print(f"选中项目类型: {type}")
  459. # 检查项目类型是否有效
  460. if type is None or type == "":
  461. QMessageBox.warning(None, "提示", "请选择有效的数据项目")
  462. return
  463. # 获取项目名称和ID
  464. item_name = self.getNodeName(self.currentItem)
  465. item_id = self.getNodeId(self.currentItem)
  466. print(f"项目名称: {item_name}, ID: {item_id}")
  467. # 准备参数
  468. params = {}
  469. try:
  470. name = self.getNodeId(self.currentItem)
  471. # 使用优化的数据库连接
  472. pg = self.get_db_connection()
  473. if pg is None:
  474. QMessageBox.warning(None, "错误", "数据库连接失败")
  475. return
  476. rows = pg.getManagerTables()
  477. for i in range(len(rows)):
  478. row = rows[i]
  479. if name == row[1]:
  480. params = {"RESTORETABLE": i}
  481. print(f"找到匹配项,参数: {params}")
  482. break
  483. # 使用优化的Redis连接
  484. redis_client = self.get_redis_client()
  485. if redis_client:
  486. try:
  487. redis_client.set("curRestoreTable", name, 60 * 30)
  488. print("Redis缓存已更新")
  489. except Exception as e:
  490. print(f"Redis操作失败: {e}")
  491. except Exception as e:
  492. print(f"准备参数失败: {e}")
  493. QMessageBox.warning(None, "错误", f"准备参数失败: {str(e)}")
  494. return
  495. # 使用QTimer延迟调用回退算法,确保UI线程准备就绪
  496. def delayed_restore():
  497. try:
  498. print("调用版本回退算法")
  499. # 检查算法是否存在
  500. registry = QgsApplication.processingRegistry()
  501. alg = registry.algorithmById("gdal:postgisrestore")
  502. if alg is None:
  503. print("算法 gdal:postgisrestore 不存在")
  504. QMessageBox.warning(None, "错误", "无法找到版本回退算法")
  505. return
  506. print("算法 gdal:postgisrestore 存在,正在打开对话框...")
  507. processing.execAlgorithmDialog("gdal:postgisrestore", params)
  508. print("版本回退对话框已打开")
  509. except Exception as e:
  510. print(f"版本回退算法调用失败: {e}")
  511. QMessageBox.warning(None, "错误", f"版本回退操作失败: {str(e)}")
  512. # 延迟执行,确保UI线程准备就绪
  513. QTimer.singleShot(100, delayed_restore)
  514. def actionImportVectorHandler(self):
  515. self.deleteMenu()
  516. print("导入矢量数据操作,调用其他工具面板")
  517. self.currentItem = self.treeWidget.currentItem()
  518. params = {}
  519. if self.currentItem is not None:
  520. name = self.getNodeName(self.currentItem)
  521. print(f"当前节点名称: {name}")
  522. pg = self.get_db_connection()
  523. if pg is None:
  524. QMessageBox.warning(None, "错误", "数据库连接失败")
  525. return
  526. try:
  527. rows = pg.getVectorZyml()
  528. print(f"获取到的行数: {len(rows)}")
  529. for i in range(len(rows)):
  530. row = rows[i]
  531. if name == row[1]:
  532. params = {"VECTOR_ZYML": i}
  533. print(f"找到匹配项,参数: {params}")
  534. break
  535. except Exception as e:
  536. print(f"获取矢量数据失败: {e}")
  537. QMessageBox.warning(None, "错误", f"获取数据失败: {str(e)}")
  538. return
  539. print(f"最终参数: {params}")
  540. # 尝试执行算法
  541. try:
  542. # 获取处理注册表
  543. registry = QgsApplication.processingRegistry()
  544. # 定义要尝试的算法名称列表(只包含实际可用的算法)
  545. algorithm_names = [
  546. "gdal:AAAimportvectorintopostgisdatabaseavailableconnections", # 主要的矢量导入算法
  547. ]
  548. # 查找可用的算法
  549. available_algorithm = None
  550. for alg_name in algorithm_names:
  551. alg = registry.algorithmById(alg_name)
  552. if alg is not None:
  553. available_algorithm = alg_name
  554. print(f"使用算法: {alg_name}")
  555. break
  556. if available_algorithm:
  557. print("正在打开算法对话框")
  558. processing.execAlgorithmDialog(available_algorithm, params)
  559. else:
  560. print("未找到任何可用的导入矢量算法")
  561. QMessageBox.warning(None, "错误", "找不到导入矢量数据的算法")
  562. except Exception as e:
  563. print(f"调用处理算法失败: {e}")
  564. QMessageBox.warning(None, "错误", f"打开对话框失败: {str(e)}")
  565. def publishserverHandler(self) -> None:
  566. """服务发布功能,调用QGIS处理算法发布服务"""
  567. print("服务发布操作,调用其他工具面板")
  568. def delayed_publish():
  569. try:
  570. print("尝试调用服务发布算法 gdal:postgistogeoserver")
  571. registry = QgsApplication.processingRegistry()
  572. alg = registry.algorithmById("gdal:postgistogeoserver")
  573. if alg is None:
  574. print("算法 gdal:postgistogeoserver 不存在")
  575. QMessageBox.warning(None, "错误", "无法找到服务发布算法(gdal:postgistogeoserver)")
  576. return
  577. print("算法存在,正在打开对话框...")
  578. processing.execAlgorithmDialog("gdal:postgistogeoserver")
  579. print("服务发布对话框已打开")
  580. except Exception as e:
  581. print(f"服务发布算法调用失败: {e}")
  582. QMessageBox.warning(None, "错误", f"服务发布操作失败: {str(e)}")
  583. QTimer.singleShot(100, delayed_publish)
  584. def actionImportRasterHandler(self):
  585. self.deleteMenu()
  586. print("导入栅格数据操作,调用其他工具面板")
  587. self.currentItem = self.treeWidget.currentItem()
  588. params = {}
  589. if self.currentItem is not None:
  590. name = self.getNodeName(self.currentItem)
  591. # **修改12: 使用优化的数据库连接**
  592. pg = self.get_db_connection()
  593. if pg is None:
  594. QMessageBox.warning(None, "错误", "数据库连接失败")
  595. return
  596. rows = pg.getVectorZyml()
  597. for i in range(len(rows)):
  598. row = rows[i]
  599. if name == row[1]:
  600. params = {"VECTOR_ZYML": i}
  601. break
  602. processing.execAlgorithmDialog("gdal:importrasterintopostgisdatabase", params)
  603. def actionExportHandler(self):
  604. self.deleteMenu()
  605. print("导出数据操作,调用其他工具面板")
  606. self.currentItem = self.treeWidget.currentItem()
  607. # 检查是否选中了项目
  608. if self.currentItem is None:
  609. QMessageBox.warning(None, "提示", "请先选择一个数据项目")
  610. return
  611. # 获取项目类型
  612. type = self.getNodeType(self.currentItem)
  613. print(f"选中项目类型: {type}")
  614. # 检查项目类型是否有效
  615. if type is None or type == "":
  616. QMessageBox.warning(None, "提示", "请选择有效的数据项目(矢量或栅格数据)")
  617. return
  618. # 获取项目名称和ID
  619. item_name = self.getNodeName(self.currentItem)
  620. item_id = self.getNodeId(self.currentItem)
  621. print(f"项目名称: {item_name}, ID: {item_id}")
  622. # 先加载图层到地图
  623. try:
  624. self.actionViewHandler()
  625. print("图层加载完成")
  626. except Exception as e:
  627. print(f"加载图层失败: {e}")
  628. QMessageBox.warning(None, "错误", f"加载图层失败: {str(e)}")
  629. return
  630. # 检查图层是否成功加载到项目中
  631. project = QgsProject.instance()
  632. layers = project.mapLayers()
  633. print(f"当前项目中的图层数量: {len(layers)}")
  634. # 查找当前图层
  635. current_layer = None
  636. for layer_id, layer in layers.items():
  637. if layer.name() == item_name:
  638. current_layer = layer
  639. print(f"找到图层: {layer.name()}, 类型: {layer.type()}")
  640. break
  641. if current_layer is None:
  642. print("警告: 未找到对应的图层,尝试直接调用导出算法")
  643. else:
  644. print(f"图层已加载: {current_layer.name()}")
  645. # 使用QTimer延迟调用导出算法,确保UI线程准备就绪
  646. def delayed_export():
  647. try:
  648. if type == "vector":
  649. print("调用矢量导出算法")
  650. # 尝试调用矢量导出算法
  651. try:
  652. # 尝试使用不同的算法名称
  653. algorithms_to_try = [
  654. "gdal:vectortoshapefile",
  655. "native:savefeatures",
  656. "gdal:vectortogeojson"
  657. ]
  658. success = False
  659. for alg_name in algorithms_to_try:
  660. try:
  661. print(f"尝试算法: {alg_name}")
  662. # 检查算法是否存在
  663. registry = QgsApplication.processingRegistry()
  664. alg = registry.algorithmById(alg_name)
  665. if alg is None:
  666. print(f"算法 {alg_name} 不存在")
  667. continue
  668. print(f"算法 {alg_name} 存在,正在打开对话框...")
  669. processing.execAlgorithmDialog(alg_name)
  670. print(f"算法 {alg_name} 对话框已打开")
  671. success = True
  672. break
  673. except Exception as alg_e:
  674. print(f"算法 {alg_name} 调用失败: {alg_e}")
  675. continue
  676. if not success:
  677. print("所有矢量导出算法都调用失败")
  678. QMessageBox.warning(None, "错误", "无法找到可用的矢量导出算法")
  679. except Exception as e:
  680. print(f"矢量导出算法调用失败: {e}")
  681. QMessageBox.warning(None, "错误", "无法找到矢量导出算法")
  682. elif type == "raster":
  683. print("调用栅格导出算法")
  684. # 尝试调用栅格导出算法
  685. try:
  686. # 尝试使用不同的算法名称
  687. algorithms_to_try = [
  688. "gdal:translate",
  689. "gdal:rasterize",
  690. "native:rasterlayerproperties"
  691. ]
  692. success = False
  693. for alg_name in algorithms_to_try:
  694. try:
  695. print(f"尝试算法: {alg_name}")
  696. # 检查算法是否存在
  697. registry = QgsApplication.processingRegistry()
  698. alg = registry.algorithmById(alg_name)
  699. if alg is None:
  700. print(f"算法 {alg_name} 不存在")
  701. continue
  702. print(f"算法 {alg_name} 存在,正在打开对话框...")
  703. processing.execAlgorithmDialog(alg_name)
  704. print(f"算法 {alg_name} 对话框已打开")
  705. success = True
  706. break
  707. except Exception as alg_e:
  708. print(f"算法 {alg_name} 调用失败: {alg_e}")
  709. continue
  710. if not success:
  711. print("所有栅格导出算法都调用失败")
  712. QMessageBox.warning(None, "错误", "无法找到可用的栅格导出算法")
  713. except Exception as e:
  714. print(f"栅格导出算法调用失败: {e}")
  715. QMessageBox.warning(None, "错误", "无法找到栅格导出算法")
  716. else:
  717. QMessageBox.warning(None, "提示", f"不支持导出类型: {type}")
  718. except Exception as e:
  719. print(f"导出操作失败: {e}")
  720. QMessageBox.warning(None, "错误", f"导出操作失败: {str(e)}")
  721. # 使用QTimer延迟执行,确保UI线程准备就绪
  722. QTimer.singleShot(100, delayed_export)
  723. def deleteMenu(self):
  724. if self.contexMenu is not None:
  725. self.contexMenu.hide()
  726. self.contexMenu.close()
  727. self.contexMenu.clear()
  728. del self.contexMenu
  729. # self.contexMenu = None
  730. def actionDeleteTableHandler(self):
  731. print("")
  732. self.deleteMenu()
  733. self.currentItem = self.treeWidget.currentItem()
  734. id = self.getNodeId(self.currentItem)
  735. question = QMessageBox.question(None, '删除确认', '确认删除此项数据资源吗?')
  736. if question == QMessageBox.Yes:
  737. # **修改13: 使用优化的数据库连接**
  738. pg = self.get_db_connection()
  739. if pg is None:
  740. QMessageBox.warning(None, "错误", "数据库连接失败")
  741. return
  742. pg.dropTable(id)
  743. # 删除元数据记录
  744. pg.deleteVectorStorage(id)
  745. self.delete_node_and_children_with_widgets(self.currentItem)
  746. def actionDeleteTableHandler1(self):
  747. print("")
  748. self.deleteMenu()
  749. self.currentItem = self.treeWidget.currentItem()
  750. id = self.getNodeId(self.currentItem)
  751. question = QMessageBox.question(None, '删除确认', '确认删除此项数据资源吗?')
  752. if question == QMessageBox.Yes:
  753. # **修改14: 使用优化的数据库连接和错误处理**
  754. try:
  755. conn_params = {
  756. 'host': "192.168.60.2",
  757. 'port': "5432",
  758. 'dbname': "datamanager",
  759. 'user': "postgres",
  760. 'password': "postgis"
  761. }
  762. conn = psycopg2.connect(**conn_params)
  763. cursor = conn.cursor()
  764. # 删除 t_vector_storage 中对应行(使用参数化查询)
  765. delete_sql = "DELETE FROM base.t_vector_storage WHERE name = %s"
  766. cursor.execute(delete_sql, (id,))
  767. conn.commit()
  768. cursor.close()
  769. conn.close()
  770. self.delete_node_and_children_with_widgets(self.currentItem)
  771. except Exception as e:
  772. QMessageBox.warning(None, "错误", f"删除操作失败: {str(e)}")
  773. def actionRenameNodeHandler(self):
  774. self.deleteMenu()
  775. self.currentItem = self.treeWidget.currentItem()
  776. id = self.getNodeId(self.currentItem)
  777. oldname = self.getNodeName(self.currentItem)
  778. newname, ok = QInputDialog.getText(QWidget(), '重命名', '请输入新名称', QLineEdit.Normal, oldname)
  779. if ok and newname != oldname:
  780. # **修改15: 使用优化的数据库连接**
  781. pg = self.get_db_connection()
  782. if pg is None:
  783. QMessageBox.warning(None, "错误", "数据库连接失败")
  784. return
  785. pg.renameZyml(id, newname)
  786. self.currentItem.setText(0, newname)
  787. def actionRenameDataSource(self):
  788. self.deleteMenu()
  789. self.currentItem = self.treeWidget.currentItem()
  790. id = self.getNodeId(self.currentItem)
  791. oldname = self.getNodeName(self.currentItem)
  792. newname, ok = QInputDialog.getText(QWidget(), '重命名', '请输入新名称', QLineEdit.Normal, oldname)
  793. if ok and newname != oldname:
  794. # **修改15: 使用优化的数据库连接**
  795. pg = self.get_db_connection()
  796. if pg is None:
  797. QMessageBox.warning(None, "错误", "数据库连接失败")
  798. return
  799. pg.renameStorage(id, newname)
  800. self.currentItem.setText(0, newname)
  801. def actionDeleteNodeHandler(self):
  802. self.deleteMenu()
  803. self.currentItem = self.treeWidget.currentItem()
  804. id = self.getNodeId(self.currentItem)
  805. print(id)
  806. question = QMessageBox.question(None, '删除确认', '确认删除此项目录吗?', QMessageBox.Yes | QMessageBox.No)
  807. if question == QMessageBox.Yes:
  808. pg = self.get_db_connection()
  809. if pg is None:
  810. QMessageBox.warning(None, "错误", "数据库连接失败")
  811. return
  812. try:
  813. pg.deleteZyml(id) # 确保这个方法执行了 DELETE 操作
  814. except Exception as e:
  815. QMessageBox.critical(None, "删除失败", f"数据库删除操作失败: {e}")
  816. pg.conn.rollback()
  817. return
  818. # 删除界面上的节点
  819. self.delete_node_and_children_with_widgets(self.currentItem)
  820. def delete_node_and_children_with_widgets(self, item):
  821. # 删除节点的所有子节点(递归)
  822. while item.childCount() > 0:
  823. child = item.child(0)
  824. self.delete_node_and_children_with_widgets(child) # 递归删除子节点
  825. # 移除小部件(如果有)
  826. self.treeWidget.removeItemWidget(item, 0) # 假设小部件在第0列
  827. if item.parent() is not None:
  828. item.parent().removeChild(item) # 删除该节点
  829. else:
  830. # 从树中删除该顶级节点
  831. index = self.treeWidget.indexOfTopLevelItem(item) # 获取该项的索引
  832. if index != -1:
  833. self.treeWidget.takeTopLevelItem(index) # 删除指定的顶级节点
  834. def actionNewNodeHandler(self):
  835. print("新建目录节点")
  836. self.deleteMenu()
  837. self.currentItem = self.treeWidget.currentItem()
  838. print(self.currentItem)
  839. name, ok = QInputDialog.getText(QWidget(), '资源目录名称', '请输入资源目录名称:')
  840. if ok:
  841. uid = self.getUUID()
  842. pid = ""
  843. if self.currentItem is not None:
  844. pid = self.getNodeId(self.currentItem)
  845. # **修改17: 使用优化的数据库连接**PostgreSQL
  846. pg = self.get_db_connection()
  847. if pg is None:
  848. QMessageBox.warning(None, "错误", "数据库连接失败")
  849. return
  850. pg.addZyml(uid, pid, name)
  851. item = None
  852. if self.currentItem is not None:
  853. item = QTreeWidgetItem(self.currentItem)
  854. else:
  855. item = QTreeWidgetItem(self.treeWidget)
  856. item.setText(0, name)
  857. item.setText(1, uid)
  858. item.setText(2, "")
  859. # === 以下为新增支持资源目录排序及保存的函数 ===
  860. def save_tree_sort_order(self):
  861. """保存树形结构的排序顺序到数据库"""
  862. pg = self.get_db_connection()
  863. if pg is None:
  864. QMessageBox.warning(None, "错误", "数据库连接失败")
  865. return False
  866. try:
  867. # 开始事务
  868. pg.execute("BEGIN")
  869. def save_node_order(item: QTreeWidgetItem, parent_bsm: str, level=0):
  870. bsm = item.text(1)
  871. sort_order = 0
  872. # 获取在父节点中的索引位置作为排序值
  873. if parent_bsm == '': # 顶级节点
  874. for i in range(self.treeWidget.topLevelItemCount()):
  875. if self.treeWidget.topLevelItem(i) == item:
  876. sort_order = i
  877. break
  878. else: # 子节点
  879. parent_item = self.find_item_by_bsm(parent_bsm)
  880. if parent_item:
  881. for i in range(parent_item.childCount()):
  882. if parent_item.child(i) == item:
  883. sort_order = i
  884. break
  885. # 更新数据库中的父节点和排序
  886. pg.execute("UPDATE t_vector_zyml SET pbsm = %s, sort = %s WHERE bsm = %s",
  887. (parent_bsm, sort_order, bsm))
  888. print(f"{' ' * level}更新节点: {item.text(0)} (bsm: {bsm}) -> 父节点: {parent_bsm or 'ROOT'}, 排序: {sort_order}")
  889. # 递归处理子节点
  890. for i in range(item.childCount()):
  891. save_node_order(item.child(i), bsm, level + 1)
  892. # 保存所有顶级节点及其子节点
  893. for i in range(self.treeWidget.topLevelItemCount()):
  894. top_item = self.treeWidget.topLevelItem(i)
  895. save_node_order(top_item, '') # 顶层节点 pbsm 为空字符串
  896. # 提交事务
  897. pg.execute("COMMIT")
  898. print("树形结构排序保存成功")
  899. return True
  900. except Exception as e:
  901. # 回滚事务
  902. pg.execute("ROLLBACK")
  903. print(f"保存树形结构排序失败: {e}")
  904. return False
  905. finally:
  906. pg.close()
  907. def find_item_by_bsm(self, bsm: str) -> QTreeWidgetItem:
  908. """根据bsm查找树形控件中的项目"""
  909. def search_item(parent_item):
  910. if parent_item.text(1) == bsm:
  911. return parent_item
  912. for i in range(parent_item.childCount()):
  913. result = search_item(parent_item.child(i))
  914. if result:
  915. return result
  916. return None
  917. # 搜索顶级项目
  918. for i in range(self.treeWidget.topLevelItemCount()):
  919. top_item = self.treeWidget.topLevelItem(i)
  920. if top_item.text(1) == bsm:
  921. return top_item
  922. result = search_item(top_item)
  923. if result:
  924. return result
  925. return None
  926. def setup_tree_drag_drop(self):
  927. """设置树形控件的拖拽功能"""
  928. # 启用拖拽功能
  929. self.treeWidget.setDragDropMode(QAbstractItemView.InternalMove)
  930. self.treeWidget.setDefaultDropAction(Qt.MoveAction)
  931. self.treeWidget.setDragEnabled(True)
  932. self.treeWidget.setAcceptDrops(True)
  933. self.treeWidget.setDropIndicatorShown(True)
  934. # 保存原始的dropEvent方法
  935. original_drop_event = self.treeWidget.dropEvent
  936. def custom_drop_event(event):
  937. """自定义拖拽放置事件处理"""
  938. def is_data_node(layertype):
  939. valid_layertypes = {"vector", "raster", "osgb", "table"}
  940. return str(layertype).strip().lower() in valid_layertypes
  941. # 获取拖拽前的信息
  942. dragged_items = self.treeWidget.selectedItems()
  943. if not dragged_items:
  944. return original_drop_event(event)
  945. dragged_item = dragged_items[0]
  946. old_parent_bsm = dragged_item.text(2) # 原父节点bsm
  947. # ==== 获取拖拽目标节点 ====
  948. drop_position = event.pos()
  949. item_at_position = self.treeWidget.itemAt(drop_position)
  950. # 确定拖拽行为
  951. target_parent_item = None
  952. drop_indicator = self.treeWidget.dropIndicatorPosition()
  953. if item_at_position is None:
  954. target_parent_item = None
  955. print("拖拽目标: 顶层")
  956. elif drop_indicator == self.treeWidget.OnItem:
  957. target_parent_item = item_at_position
  958. print(f"拖拽目标: 作为 '{item_at_position.text(0)}' 的子节点")
  959. else:
  960. target_parent_item = item_at_position.parent()
  961. print(
  962. f"拖拽目标: 与 '{item_at_position.text(0)}' 成为兄弟节点,父节点是 '{target_parent_item.text(0) if target_parent_item else 'ROOT'}'")
  963. # 获取节点类型信息
  964. dragged_layertype = self.getNodeType(dragged_item)
  965. target_layertype = self.getNodeType(target_parent_item) if target_parent_item else None
  966. # 判断是否为数据节点
  967. is_dragged_data_node = is_data_node(dragged_layertype)
  968. is_target_data_node = is_data_node(target_layertype)
  969. # ==== 拖拽限制 ====
  970. if is_dragged_data_node:
  971. if target_parent_item is None:
  972. QMessageBox.warning(None, "无效操作", "❌ 数据节点不能拖动为顶层节点,请拖入目录节点中。")
  973. return
  974. if is_target_data_node:
  975. QMessageBox.warning(None, "无效操作", "❌ 数据节点不能拖动到其他数据节点下,请拖入目录节点中。")
  976. return
  977. print("✅ 允许数据节点拖到目录节点下")
  978. else:
  979. if is_target_data_node:
  980. QMessageBox.warning(None, "无效操作", "❌ 目录节点不能拖动到数据节点下。")
  981. return
  982. print("✅ 允许目录节点拖拽操作")
  983. # ==== 执行拖拽 ====
  984. original_drop_event(event)
  985. # 更新拖拽后信息
  986. new_parent_item = dragged_item.parent()
  987. new_parent_bsm = new_parent_item.text(1) if new_parent_item else ''
  988. dragged_item.setText(2, new_parent_bsm)
  989. if is_dragged_data_node:
  990. print(
  991. f"数据节点 '{dragged_item.text(0)}' 父节点从 {old_parent_bsm or 'ROOT'} 移动到 {new_parent_bsm or 'ROOT'}")
  992. self.handle_drag_operation("update_parent_only", dragged_item, old_parent_bsm, new_parent_bsm)
  993. else:
  994. # 仅目录节点需要记录排序
  995. old_position = -1
  996. new_position = -1
  997. if old_parent_bsm == '':
  998. for i in range(self.treeWidget.topLevelItemCount()):
  999. if self.treeWidget.topLevelItem(i) == dragged_item:
  1000. old_position = i
  1001. break
  1002. else:
  1003. old_parent_item = self.find_item_by_bsm(old_parent_bsm)
  1004. if old_parent_item:
  1005. for i in range(old_parent_item.childCount()):
  1006. if old_parent_item.child(i) == dragged_item:
  1007. old_position = i
  1008. break
  1009. if new_parent_item is None:
  1010. for i in range(self.treeWidget.topLevelItemCount()):
  1011. if self.treeWidget.topLevelItem(i) == dragged_item:
  1012. new_position = i
  1013. break
  1014. else:
  1015. for i in range(new_parent_item.childCount()):
  1016. if new_parent_item.child(i) == dragged_item:
  1017. new_position = i
  1018. break
  1019. drag_type = self.get_drag_type(old_parent_bsm, new_parent_bsm, old_position, new_position)
  1020. print(f"拖拽类型: {drag_type}")
  1021. print(
  1022. f"目录节点 '{dragged_item.text(0)}' 从 {old_parent_bsm or 'ROOT'}[{old_position}] 移动到 {new_parent_bsm or 'ROOT'}[{new_position}]")
  1023. self.handle_drag_operation(drag_type, dragged_item, old_parent_bsm, new_parent_bsm)
  1024. # 替换 dropEvent 方法
  1025. self.treeWidget.dropEvent = custom_drop_event
  1026. def get_drag_type(self, old_parent_bsm, new_parent_bsm, old_position, new_position):
  1027. """判断拖拽操作类型"""
  1028. if old_parent_bsm == new_parent_bsm:
  1029. return "同级排序" # 同级目录拖动改变顺序
  1030. elif old_parent_bsm == None and new_parent_bsm != None:
  1031. return "父变子" # 父节点变子节点
  1032. elif old_parent_bsm != None and new_parent_bsm == None:
  1033. return "子变父" # 子节点变父节点
  1034. else:
  1035. return "跨级移动" # 不同层级间的移动
  1036. def handle_drag_operation(self, drag_type, dragged_item, old_parent_bsm, new_parent_bsm):
  1037. """处理不同类型的拖拽操作并实时保存"""
  1038. print(f"\n=== 开始处理拖拽操作: {drag_type} ===")
  1039. pg = self.get_db_connection()
  1040. if pg is None:
  1041. print("错误: 无法获取数据库连接")
  1042. QMessageBox.warning(None, "错误", "数据库连接失败")
  1043. return False
  1044. try:
  1045. pg.execute("SELECT 1") # 测试连接
  1046. pg.execute("BEGIN")
  1047. print("事务已开始")
  1048. dragged_bsm = dragged_item.text(1)
  1049. print(f"被拖动节点 BSM: {dragged_bsm}")
  1050. # 查询拖拽前的数据库状态
  1051. pg.execute("SELECT pbsm, sort FROM t_vector_zyml WHERE bsm = %s", (dragged_bsm,))
  1052. print(f"拖拽前状态: {pg.fetchone()}")
  1053. success = False
  1054. if drag_type == "同级排序":
  1055. success = self.update_sibling_sort_order(pg, new_parent_bsm)
  1056. elif drag_type in ["父变子", "子变父", "跨级移动"]:
  1057. # 更新 t_vector_zyml 的 pbsm
  1058. update_sql = "UPDATE t_vector_zyml SET pbsm = %s WHERE bsm = %s"
  1059. pg.execute(update_sql, (new_parent_bsm, dragged_bsm))
  1060. print(f"更新 t_vector_zyml: bsm={dragged_bsm}, pbsm={new_parent_bsm}")
  1061. # ➕ 如果是数据节点,还要更新 t_vector_storage.xmlx 字段
  1062. dragged_layertype = self.getNodeType(dragged_item)
  1063. if dragged_layertype and dragged_layertype.strip() != "":
  1064. update_storage_sql = "UPDATE base.t_vector_storage SET xmlx = %s WHERE name = %s"
  1065. dragged_name = dragged_item.text(1) # name = bsm
  1066. print(f"同步更新 t_vector_storage.xmlx -> {dragged_name} 为目录 {new_parent_bsm}")
  1067. pg.execute(update_storage_sql, (new_parent_bsm, dragged_name))
  1068. pg.execute("SELECT xmlx FROM base.t_vector_storage WHERE name = %s", (dragged_name,))
  1069. print(f"验证更新结果: {pg.fetchone()}")
  1070. # 同步排序
  1071. success1 = self.update_sibling_sort_order(pg, new_parent_bsm)
  1072. success2 = self.update_sibling_sort_order(pg, old_parent_bsm)
  1073. success = success1 and success2
  1074. if success:
  1075. pg.execute("COMMIT")
  1076. print("事务提交成功 ✅ 拖拽操作已保存")
  1077. return True
  1078. else:
  1079. pg.execute("ROLLBACK")
  1080. print("事务回滚 ❌ 拖拽保存失败")
  1081. return False
  1082. except Exception as e:
  1083. try:
  1084. pg.execute("ROLLBACK")
  1085. except:
  1086. pass
  1087. print(f"异常: {e}")
  1088. import traceback
  1089. print(traceback.format_exc())
  1090. QMessageBox.warning(None, "错误", f"保存失败: {str(e)}")
  1091. return False
  1092. finally:
  1093. pg.close()
  1094. print("数据库连接已关闭")
  1095. print("=== 拖拽处理结束 ===\n")
  1096. def update_sibling_sort_order(self, pg, parent_bsm):
  1097. """更新指定父节点下所有子节点的排序"""
  1098. try:
  1099. print(f" >>> 开始更新父节点 '{parent_bsm or 'ROOT'}' 下的子节点排序...")
  1100. # 获取界面中的实际排序
  1101. sibling_items = []
  1102. if parent_bsm == '': # 顶级节点
  1103. for i in range(self.treeWidget.topLevelItemCount()):
  1104. item = self.treeWidget.topLevelItem(i)
  1105. sibling_items.append((item.text(1), i, item.text(0))) # (bsm, sort_order, name)
  1106. else: # 子节点
  1107. parent_item = self.find_item_by_bsm(parent_bsm)
  1108. if parent_item:
  1109. for i in range(parent_item.childCount()):
  1110. item = parent_item.child(i)
  1111. sibling_items.append((item.text(1), i, item.text(0))) # (bsm, sort_order, name)
  1112. else:
  1113. print(f" !!! 未找到父节点: {parent_bsm}")
  1114. return False
  1115. print(f" >>> 找到 {len(sibling_items)} 个同级节点需要更新排序")
  1116. # 批量更新排序
  1117. update_count = 0
  1118. for bsm, sort_order, name in sibling_items:
  1119. update_sql = "UPDATE t_vector_zyml SET sort = %s WHERE bsm = %s"
  1120. print(f" -> 更新节点 {name}({bsm}) 的排序为 {sort_order}")
  1121. pg.execute(update_sql, (sort_order, bsm))
  1122. affected_rows = pg.rowcount if hasattr(pg, 'rowcount') else 'unknown'
  1123. print(f" 影响行数: {affected_rows}")
  1124. # 验证单个更新是否成功
  1125. pg.execute("SELECT sort FROM t_vector_zyml WHERE bsm = %s", (bsm,))
  1126. verify_result = pg.fetchone()
  1127. if verify_result:
  1128. actual_sort = verify_result[0]
  1129. if actual_sort == sort_order:
  1130. print(f" ✓ 验证成功: {actual_sort}")
  1131. update_count += 1
  1132. else:
  1133. print(f" ✗ 验证失败: 期望{sort_order}, 实际{actual_sort}")
  1134. else:
  1135. print(f" ✗ 验证失败: 未找到记录")
  1136. print(f" >>> 完成父节点 '{parent_bsm or 'ROOT'}' 下的排序更新,成功更新 {update_count}/{len(sibling_items)} 个节点")
  1137. return update_count == len(sibling_items)
  1138. except Exception as e:
  1139. print(f" !!! 更新同级排序失败: {e}")
  1140. import traceback
  1141. print(f" !!! 详细错误: {traceback.format_exc()}")
  1142. return False
  1143. def sort_tree_items(self, parent_item):
  1144. """对指定父节点下的子项进行排序"""
  1145. child_count = parent_item.childCount()
  1146. if child_count <= 1:
  1147. return
  1148. children = [parent_item.child(i) for i in range(child_count)]
  1149. def sort_key(item):
  1150. try:
  1151. return int(item.data(0, Qt.UserRole + 1))
  1152. except:
  1153. return 9999
  1154. children.sort(key=sort_key)
  1155. # 清除原有子项并重新添加
  1156. for i in reversed(range(child_count)):
  1157. parent_item.takeChild(i)
  1158. for child in children:
  1159. parent_item.addChild(child)
  1160. def initializeTreeData(self):
  1161. """初始化树形数据"""
  1162. if not self._tree_initialized:
  1163. try:
  1164. tree = Tree()
  1165. temp_tree = tree.initTreeWidget(self.dbcoon)
  1166. # 清空现有数据
  1167. self.treeWidget.clear()
  1168. # 添加新数据
  1169. for i in range(temp_tree.topLevelItemCount()):
  1170. item = temp_tree.topLevelItem(i).clone()
  1171. self.treeWidget.addTopLevelItem(item)
  1172. self.sort_tree_items(item) # 对一级节点排序其子项
  1173. # 设置拖拽功能
  1174. self.setup_tree_drag_drop()
  1175. self._tree_initialized = True
  1176. print("树形数据初始化完成,拖拽功能已启用")
  1177. except Exception as e:
  1178. print(f"初始化树形数据失败: {e}")
  1179. error_item = QTreeWidgetItem(self.treeWidget)
  1180. error_item.setText(0, "数据加载失败,请点击刷新重试")
  1181. def refreshTreeWidget(self):
  1182. """刷新树形控件"""
  1183. try:
  1184. print("开始刷新树形控件...")
  1185. # 清空并重新初始化
  1186. self.treeWidget.clear()
  1187. self._tree_initialized = False
  1188. self.initializeTreeData()
  1189. print("树形控件刷新完成")
  1190. except Exception as e:
  1191. print(f"刷新树形控件失败: {e}")
  1192. QMessageBox.warning(None, "错误", f"刷新失败: {str(e)}")
  1193. def on_tree_item_dropped(self):
  1194. self.save_tree_sort_order()
  1195. def getNodeType(self, node):
  1196. return node.text(2)
  1197. def getUUID(self):
  1198. id = uuid.uuid4().__str__()
  1199. return id.replace("-", "")
  1200. def getNodeName(self, node):
  1201. return node.text(0)
  1202. def getNodeId(self, node):
  1203. return node.text(1)
  1204. def actionAttrHandler(self):
  1205. print("元数据信息")
  1206. self.deleteMenu()
  1207. if self.treeWidget.currentItem():
  1208. self.currentItem = self.treeWidget.currentItem()
  1209. print(self.currentItem)
  1210. layertype = self.getNodeType(self.currentItem)
  1211. print(layertype)
  1212. if layertype is not None and layertype != "": # 选择的是数据项
  1213. tablename = self.getNodeId(self.currentItem)
  1214. # **修改18: 使用优化的数据库连接**
  1215. # 获取元数据信息
  1216. pg = self.get_db_connection()
  1217. if pg is None:
  1218. QMessageBox.warning(None, "错误", "数据库连接失败")
  1219. return
  1220. self.tableattr = pg.getResourceAttr(tablename)
  1221. dia = FormDialog(self.tableattr)
  1222. dia.exec_()
  1223. else: # 选择的是目录
  1224. QMessageBox.information(self, "提示信息", "请选择数据项")
  1225. def on_item_double_clicked(self):
  1226. self.actionViewHandler()
  1227. # 属性表
  1228. def actionAttrTableHandler(self):
  1229. self.deleteMenu()
  1230. if self.treeWidget.currentItem():
  1231. self.currentItem = self.treeWidget.currentItem()
  1232. layertype = self.getNodeType(self.currentItem)
  1233. print(layertype)
  1234. if layertype is not None and layertype != "": # 选择的是数据项
  1235. tablename = self.getNodeId(self.currentItem)
  1236. print(tablename)
  1237. pglayer = None
  1238. if layertype == "vector":
  1239. pglayer = self.add_postgis_vector(tablename, True)
  1240. elif layertype == "raster":
  1241. pglayer = self.add_postgis_raster(tablename, True)
  1242. # if pglayer is not None and pglayer.isValid() and pglayer.fields():
  1243. # iface.showAttributeTable(pglayer) # Show the attribute table
  1244. # else:
  1245. # print("No valid layer selected or layer has no attributes.")
  1246. if pglayer:
  1247. dialog = CombinedDialog(pglayer)
  1248. dialog.showDialog()
  1249. else:
  1250. print("No layer selected")
  1251. # 图层属性
  1252. def actionLayerAttrHandler(self):
  1253. self.deleteMenu()
  1254. if self.treeWidget.currentItem():
  1255. self.currentItem = self.treeWidget.currentItem()
  1256. layertype = self.getNodeType(self.currentItem)
  1257. print(layertype)
  1258. if layertype is not None and layertype != "": # 选择的是数据项
  1259. tablename = self.getNodeId(self.currentItem)
  1260. print(tablename)
  1261. pglayer = None
  1262. if layertype == "vector":
  1263. pglayer = self.add_postgis_vector(tablename, True)
  1264. elif layertype == "raster":
  1265. pglayer = self.add_postgis_raster(tablename, True)
  1266. # if pglayer is not None:
  1267. # iface.showLayerProperties(pglayer)
  1268. if pglayer:
  1269. dialog = CombinedDialog(pglayer)
  1270. dialog.showDialog()
  1271. else:
  1272. print("No layer selected")
  1273. def actionViewHandler(self):
  1274. print(self.contexMenu)
  1275. self.deleteMenu()
  1276. if self.treeWidget.currentItem():
  1277. self.currentItem = self.treeWidget.currentItem()
  1278. print(self.currentItem)
  1279. layertype = self.getNodeType(self.currentItem)
  1280. print(layertype)
  1281. if layertype is not None and layertype != "": # 选择的是数据项
  1282. tablename = self.getNodeId(self.currentItem)
  1283. print(tablename)
  1284. if layertype == "vector":
  1285. self.add_postgis_vector(tablename, False)
  1286. elif layertype == "raster":
  1287. self.add_postgis_raster(tablename, False)
  1288. elif layertype == "osgb":
  1289. self.load_osgb_layer(tablename)
  1290. elif layertype == "table":
  1291. self.load_table_layer(tablename)
  1292. def actionstaticHandler(self):
  1293. # **修改19: 优化统计功能,添加异常处理和连接超时**
  1294. import os
  1295. import psycopg2
  1296. import matplotlib.pyplot as plt
  1297. from matplotlib import font_manager
  1298. from PyQt5.QtWidgets import (
  1299. QDialog, QVBoxLayout, QMessageBox, QPushButton,
  1300. QFileDialog, QHBoxLayout, QLabel, QGridLayout
  1301. )
  1302. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  1303. from PyQt5.QtCore import Qt
  1304. from qgis.utils import iface
  1305. def set_matplotlib_font():
  1306. try:
  1307. font_path = 'C:/Windows/Fonts/msyh.ttc'
  1308. prop = font_manager.FontProperties(fname=font_path)
  1309. plt.rcParams['font.family'] = prop.get_name()
  1310. except Exception as e:
  1311. print(f"字体设置失败:{e}")
  1312. def get_xmlx_name_mapping(cursor):
  1313. xmlx_name_map = {}
  1314. cursor.execute("SELECT bsm, name, pbsm FROM base.t_vector_zyml")
  1315. rows = cursor.fetchall()
  1316. pbsm_map = {}
  1317. for bsm, name, pbsm in rows:
  1318. if bsm:
  1319. xmlx_name_map[bsm] = name
  1320. if pbsm:
  1321. pbsm_map[bsm] = pbsm
  1322. for bsm, pbsm in pbsm_map.items():
  1323. if pbsm in xmlx_name_map:
  1324. xmlx_name_map[bsm] = xmlx_name_map[pbsm]
  1325. return xmlx_name_map
  1326. def show_matplotlib_dialog(fig, total_size_mb, total_count, glbm_count, xmlx_count_unique):
  1327. dialog = QDialog(iface.mainWindow())
  1328. dialog.setWindowTitle("数据统计")
  1329. layout = QVBoxLayout()
  1330. top_bar = QHBoxLayout()
  1331. export_btn = QPushButton("导出图表")
  1332. export_btn.setFixedWidth(100)
  1333. export_btn.setCursor(Qt.PointingHandCursor)
  1334. def export_chart():
  1335. save_path, _ = QFileDialog.getSaveFileName(
  1336. dialog, "保存图表", "", "PNG 图片 (*.png);;PDF 文件 (*.pdf)"
  1337. )
  1338. if save_path:
  1339. try:
  1340. fig.savefig(save_path, dpi=300)
  1341. QMessageBox.information(dialog, "成功", f"图表已导出到:\n{save_path}")
  1342. except Exception as e:
  1343. QMessageBox.critical(dialog, "导出失败", f"导出图表时出错:{str(e)}")
  1344. export_btn.clicked.connect(export_chart)
  1345. top_bar.addStretch()
  1346. top_bar.addWidget(export_btn)
  1347. layout.addLayout(top_bar)
  1348. info_grid = QGridLayout()
  1349. info_titles = ["总体数据量", "总体数据大小", "涉及部门", "数据分类"]
  1350. info_values = [f"{total_count} 条", f"{total_size_mb:.2f} MB", f"{glbm_count} 个",
  1351. f"{xmlx_count_unique} 个"]
  1352. for i, (title, val) in enumerate(zip(info_titles, info_values)):
  1353. label_title = QLabel(f"<b>{title}:</b>")
  1354. label_value = QLabel(val)
  1355. info_grid.addWidget(label_title, 0, i, alignment=Qt.AlignCenter)
  1356. info_grid.addWidget(label_value, 1, i, alignment=Qt.AlignCenter)
  1357. layout.addLayout(info_grid)
  1358. canvas = FigureCanvas(fig)
  1359. layout.addWidget(canvas)
  1360. dialog.setLayout(layout)
  1361. dialog.resize(1200, 900)
  1362. dialog.exec_()
  1363. try:
  1364. # **修改20: 添加连接超时和错误处理**
  1365. conn = psycopg2.connect(
  1366. host='192.168.60.2', port='5432',
  1367. dbname='datamanager', user='postgres', password='postgis',
  1368. connect_timeout=10) # 添加连接超时
  1369. conn.autocommit = True
  1370. cursor = conn.cursor()
  1371. schema = 'base'
  1372. table = 't_vector_storage'
  1373. full_table = f'"{schema}"."{table}"'
  1374. xmlx_name_map = get_xmlx_name_mapping(cursor)
  1375. cursor.execute(f"SELECT xmlx FROM {full_table} WHERE xmlx IS NOT NULL")
  1376. xmlx_values = cursor.fetchall()
  1377. xmlx_count_map = {}
  1378. for (x,) in xmlx_values:
  1379. name = xmlx_name_map.get(x, str(x))
  1380. xmlx_count_map[name] = xmlx_count_map.get(name, 0) + 1
  1381. stat_results = {'xmlx': sorted(xmlx_count_map.items(), key=lambda x: x[1], reverse=True)}
  1382. for field in ['sjlx', 'glbm']:
  1383. cursor.execute(f"""
  1384. SELECT {field}, COUNT(*) FROM {full_table}
  1385. WHERE {field} IS NOT NULL
  1386. GROUP BY {field}
  1387. ORDER BY COUNT(*) DESC
  1388. """)
  1389. stat_results[field] = cursor.fetchall()
  1390. cursor.execute(f"SELECT sjlx, name, sjywz, xmlx, glbm FROM {full_table}")
  1391. rows = cursor.fetchall()
  1392. size_map = {'vector': 0, 'raster': 0, 'table': 0, 'osgb': 0}
  1393. size_map_by_field = {'xmlx': {}, 'sjlx': {}, 'glbm': {}}
  1394. sjlx_ch_map = {'vector': '矢量', 'raster': '栅格', 'osgb': '三维', 'table': '附件'}
  1395. for sjlx, name, sjywz, xmlx, glbm in rows:
  1396. size = 0
  1397. try:
  1398. if sjlx == 'osgb' and sjywz and os.path.exists(sjywz):
  1399. if os.path.isdir(sjywz):
  1400. for dirpath, _, filenames in os.walk(sjywz):
  1401. for fname in filenames:
  1402. fpath = os.path.join(dirpath, fname)
  1403. if os.path.exists(fpath):
  1404. size += os.path.getsize(fpath)
  1405. else:
  1406. size = os.path.getsize(sjywz)
  1407. elif sjlx in ['vector', 'raster']:
  1408. if not name:
  1409. continue
  1410. for tname in [name, f'"{name}"', f'"{schema}"."{name}"']:
  1411. try:
  1412. cursor.execute("SELECT pg_total_relation_size(%s);", (tname,))
  1413. res = cursor.fetchone()
  1414. if res and res[0]:
  1415. size = res[0]
  1416. break
  1417. except Exception:
  1418. continue
  1419. elif sjlx == 'table' and sjywz:
  1420. for tname in [sjywz, f'"{sjywz}"', f'"{schema}"."{sjywz}"']:
  1421. try:
  1422. cursor.execute("SELECT pg_total_relation_size(%s);", (tname,))
  1423. res = cursor.fetchone()
  1424. if res and res[0]:
  1425. size = res[0]
  1426. break
  1427. except Exception:
  1428. continue
  1429. except Exception:
  1430. pass
  1431. size_mb = size / 1024 / 1024
  1432. size_map[sjlx] = size_map.get(sjlx, 0) + size_mb
  1433. xmlx_name = xmlx_name_map.get(xmlx, str(xmlx)) if xmlx else "未知"
  1434. size_map_by_field['xmlx'][xmlx_name] = size_map_by_field['xmlx'].get(xmlx_name, 0) + size_mb
  1435. if sjlx:
  1436. ch_sjlx = sjlx_ch_map.get(sjlx, sjlx)
  1437. size_map_by_field['sjlx'][ch_sjlx] = size_map_by_field['sjlx'].get(ch_sjlx, 0) + size_mb
  1438. if glbm:
  1439. size_map_by_field['glbm'][glbm] = size_map_by_field['glbm'].get(glbm, 0) + size_mb
  1440. total_size_mb = sum(size_map.values())
  1441. cursor.execute(f"SELECT COUNT(*) FROM {full_table}")
  1442. total_count = cursor.fetchone()[0]
  1443. glbm_count = len(stat_results['glbm'])
  1444. xmlx_count_unique = len(stat_results['xmlx'])
  1445. set_matplotlib_font()
  1446. fig, axes = plt.subplots(2, 3, figsize=(18, 10))
  1447. field_names = {'xmlx': '数据分类', 'sjlx': '数据类型', 'glbm': '管理部门'}
  1448. fields = ['xmlx', 'sjlx', 'glbm']
  1449. for idx, field in enumerate(fields):
  1450. data = stat_results.get(field, [])
  1451. labels = [sjlx_ch_map.get(str(r[0]), str(r[0])) if field == 'sjlx' else str(r[0]) for r in data]
  1452. sizes = [r[1] for r in data]
  1453. ax = axes[0][idx]
  1454. if not sizes or sum(sizes) == 0:
  1455. ax.axis('off')
  1456. ax.set_title(f"{field_names[field]}(无数据)")
  1457. continue
  1458. if len(labels) > 6:
  1459. others_sum = sum(sizes[5:])
  1460. labels = labels[:5] + ["其他"]
  1461. sizes = sizes[:5] + [others_sum]
  1462. wedges, texts, autotexts = ax.pie(
  1463. sizes, labels=labels, autopct='%1.1f%%', startangle=90,
  1464. wedgeprops=dict(width=0.4), textprops={'fontsize': 9})
  1465. for i, autotext in enumerate(autotexts):
  1466. autotext.set_text(f'{sizes[i]}')
  1467. ax.axis('equal')
  1468. ax.set_title(f"{field_names[field]}", fontsize=12)
  1469. ax.legend(labels, loc='best', fontsize=8)
  1470. for idx, field in enumerate(fields):
  1471. ax = axes[1][idx]
  1472. field_data = size_map_by_field[field]
  1473. if not field_data:
  1474. ax.axis('off')
  1475. ax.set_title(f"{field_names[field]}(无数据)")
  1476. continue
  1477. sorted_items = sorted(field_data.items(), key=lambda x: x[1], reverse=True)
  1478. if len(sorted_items) > 6:
  1479. others = sum(v for _, v in sorted_items[5:])
  1480. sorted_items = sorted_items[:5] + [("其他", others)]
  1481. labels = [str(k) for k, _ in sorted_items]
  1482. values = [round(v, 2) for _, v in sorted_items]
  1483. bars = ax.bar(labels, values, color='steelblue', width=0.5, edgecolor='black')
  1484. ax.set_title(f"{field_names[field]}", fontsize=12)
  1485. ax.set_ylabel("大小 (MB)")
  1486. ax.tick_params(axis='x', rotation=30)
  1487. ax.set_ylim(0, max(values) * 1.2)
  1488. for bar in bars:
  1489. h = bar.get_height()
  1490. ax.annotate(f'{h:.2f}', xy=(bar.get_x() + bar.get_width() / 2, h),
  1491. xytext=(0, 3), textcoords="offset points",
  1492. ha='center', va='bottom', fontsize=9)
  1493. fig.suptitle("数据量与存储大小统计", fontsize=16)
  1494. plt.tight_layout(rect=[0, 0.05, 1, 0.95])
  1495. show_matplotlib_dialog(fig, total_size_mb, total_count, glbm_count, xmlx_count_unique)
  1496. cursor.close()
  1497. conn.close()
  1498. except Exception as e:
  1499. QMessageBox.critical(iface.mainWindow(), "错误", f"统计失败: {str(e)}")
  1500. def load_table_layer(self, name: str, layer_name: str = None):
  1501. """
  1502. 根据 name(源文件名)从 t_vector_storage 获取 sjywz,加载 PostgreSQL 非空间表。
  1503. """
  1504. # **修改21: 优化数据库连接和错误处理**
  1505. try:
  1506. conn = psycopg2.connect(
  1507. host="192.168.60.2",
  1508. port="5432",
  1509. dbname="datamanager",
  1510. user="postgres",
  1511. password="postgis",
  1512. connect_timeout=10
  1513. )
  1514. cursor = conn.cursor()
  1515. # 按 name 字段查询(模糊匹配也可以加)
  1516. cursor.execute(
  1517. "SELECT sjywz FROM base.t_vector_storage WHERE name = %s ORDER BY rksj DESC LIMIT 1",
  1518. (name,))
  1519. result = cursor.fetchone()
  1520. if not result:
  1521. raise Exception(f"未找到 name 为 {name} 的记录。")
  1522. sjywz = result[0]
  1523. print(f"查到的 sjywz: {sjywz}")
  1524. if '.' not in sjywz:
  1525. raise Exception(f"sjywz 字段格式非法:{sjywz},应为 schema.tablename")
  1526. schema, table = sjywz.split('.')
  1527. if layer_name is None:
  1528. layer_name = table
  1529. uri = (
  1530. f"dbname='datamanager' host=192.168.60.2 port=5432 user='postgres' "
  1531. f"password='postgis' sslmode=disable table=\"{schema}\".\"{table}\" sql="
  1532. )
  1533. layer = QgsVectorLayer(uri, layer_name, "postgres")
  1534. if not layer.isValid():
  1535. raise Exception(f"表格图层加载失败:{schema}.{table}")
  1536. QgsProject.instance().addMapLayer(layer)
  1537. iface.showAttributeTable(layer)
  1538. cursor.close()
  1539. conn.close()
  1540. return layer
  1541. except Exception as e:
  1542. QMessageBox.warning(None, "错误", f"加载表格数据失败: {str(e)}")
  1543. return None
  1544. def load_osgb_layer(self, name):
  1545. print(f"加载三维数据(OSGB): {name}")
  1546. # **修改22: 优化OSGB数据加载,添加错误处理**
  1547. try:
  1548. # 连接到 PostgreSQL 数据库
  1549. conn_params = {
  1550. 'host': "192.168.60.2",
  1551. 'port': "5432",
  1552. 'dbname': "datamanager",
  1553. 'user': "postgres",
  1554. 'password': "postgis",
  1555. 'connect_timeout': 10
  1556. }
  1557. # 连接数据库
  1558. conn = psycopg2.connect(**conn_params)
  1559. cursor = conn.cursor()
  1560. # 查询数据库获取路径
  1561. cursor.execute("SELECT sjywz FROM base.t_vector_storage WHERE name = %s", (name,))
  1562. result = cursor.fetchone()
  1563. cursor.close()
  1564. conn.close()
  1565. if result is None:
  1566. QMessageBox.warning(None, "错误", f"未找到三维数据路径: {name}")
  1567. return
  1568. osgb_dir = result[0] # 获取路径
  1569. except Exception as e:
  1570. QMessageBox.warning(None, "错误", f"数据库连接失败或查询错误: {str(e)}")
  1571. return
  1572. # 检查路径是否存在
  1573. if not os.path.exists(osgb_dir):
  1574. QMessageBox.warning(None, "错误", f"三维数据目录不存在: {osgb_dir}")
  1575. return
  1576. # 构造命令行命令来打开 OSGB 文件
  1577. dasviewer_path = r"D:\app\DasViewer.exe"
  1578. # **修改23: 检查DasViewer是否存在**
  1579. if not os.path.exists(dasviewer_path):
  1580. QMessageBox.warning(None, "错误", f"DasViewer程序不存在: {dasviewer_path}")
  1581. return
  1582. command = [dasviewer_path, osgb_dir]
  1583. try:
  1584. # 使用 subprocess 启动 DasViewer
  1585. subprocess.run(command, check=True)
  1586. QMessageBox.information(None, "提示", f"已启动 DasViewer 并加载三维数据: {osgb_dir}")
  1587. except subprocess.CalledProcessError as e:
  1588. QMessageBox.warning(None, "错误", f"打开 DasViewer 时出错: {str(e)}")
  1589. except Exception as e:
  1590. QMessageBox.warning(None, "错误", f"发生错误: {str(e)}")
  1591. def add_local_vector_layer(self, layer_path, layer_name):
  1592. # 添加矢量图层
  1593. vector_layer = QgsVectorLayer(layer_path, layer_name, "ogr")
  1594. # 将图层添加到项目中
  1595. QgsProject.instance().addMapLayer(vector_layer)
  1596. self.zoomToLayer(vector_layer)
  1597. def add_postgis_vector(self, tablename, getlayer):
  1598. # **修改24: 优化PostGIS矢量图层加载,添加错误处理**
  1599. try:
  1600. # 创建PostgreSQL图层
  1601. pgtable = tablename.split(".")
  1602. postgresLayer = QgsVectorLayer(
  1603. "dbname='{}' host={} port={} user='{}' password='{}' sslmode=disable checkPrimaryKeyUnicity='1' table=\"{}\".\"{}\" (geom)".format(
  1604. self.dbcoon["dbname"],
  1605. self.dbcoon["host"],
  1606. self.dbcoon["port"],
  1607. self.dbcoon["user"],
  1608. self.dbcoon["password"],
  1609. pgtable[0],
  1610. pgtable[1]
  1611. ),
  1612. pgtable[1], 'postgres')
  1613. if not postgresLayer.isValid():
  1614. QMessageBox.warning(None, "错误", f"无法加载矢量图层: {tablename}")
  1615. return None
  1616. if getlayer == True:
  1617. return postgresLayer
  1618. # 添加图层到QGIS项目
  1619. QgsProject.instance().addMapLayer(postgresLayer)
  1620. self.zoomToLayer(postgresLayer)
  1621. return postgresLayer
  1622. except Exception as e:
  1623. QMessageBox.warning(None, "错误", f"加载矢量图层失败: {str(e)}")
  1624. return None
  1625. def add_postgis_raster(self, tablename, getlayer):
  1626. # **修改25: 优化PostGIS栅格图层加载,添加错误处理**
  1627. try:
  1628. # 创建PostgreSQL图层
  1629. pgtable = tablename.split(".")
  1630. postgresLayer = QgsRasterLayer(
  1631. "dbname='{}' host={} port={} user='{}' password='{}' sslmode=disable checkPrimaryKeyUnicity='1' table=\"{}\".\"{}\" (rast)".format(
  1632. self.dbcoon["dbname"],
  1633. self.dbcoon["host"],
  1634. self.dbcoon["port"],
  1635. self.dbcoon["user"],
  1636. self.dbcoon["password"],
  1637. pgtable[0],
  1638. pgtable[1]
  1639. ),
  1640. pgtable[1], "postgresraster")
  1641. if not postgresLayer.isValid():
  1642. QMessageBox.warning(None, "错误", f"无法加载栅格图层: {tablename}")
  1643. return None
  1644. if getlayer == True:
  1645. return postgresLayer
  1646. # 添加图层到QGIS项目
  1647. QgsProject.instance().addMapLayer(postgresLayer)
  1648. self.zoomToLayer(postgresLayer)
  1649. return postgresLayer
  1650. except Exception as e:
  1651. QMessageBox.warning(None, "错误", f"加载栅格图层失败: {str(e)}")
  1652. return None
  1653. def add_local_raster_layer(self, layer_path, layer_name):
  1654. # 添加栅格图层
  1655. raster_layer = QgsRasterLayer(layer_path, layer_name)
  1656. # 将图层添加到项目中
  1657. QgsProject.instance().addMapLayer(raster_layer)
  1658. self.zoomToLayer(raster_layer)
  1659. def zoomToLayer(self, layer):
  1660. # 当前活动图层
  1661. activelayer = iface.activeLayer()
  1662. # 缩放到图层范围
  1663. # 获取当前项目的 CRS
  1664. current_canvas_crs = QgsProject.instance().crs()
  1665. current_layer_crs = layer.crs()
  1666. if activelayer is None or activelayer == layer or current_canvas_crs.authid() == current_layer_crs.authid():
  1667. iface.mapCanvas().setExtent(layer.extent())
  1668. else:
  1669. print(current_canvas_crs.authid())
  1670. print(current_layer_crs.authid())
  1671. rect = layer.extent()
  1672. print(rect)
  1673. # 创建坐标转换器
  1674. transform = QgsCoordinateTransform(current_layer_crs, current_canvas_crs, QgsProject.instance())
  1675. print(current_layer_crs)
  1676. print(current_layer_crs)
  1677. # 使用坐标转换器将 QgsRectangle 转换为目标 CRS
  1678. # 我们需要将矩形的四个角进行转换
  1679. rect_min_x, rect_min_y = transform.transform(rect.xMinimum(), rect.yMinimum())
  1680. rect_max_x, rect_max_y = transform.transform(rect.xMaximum(), rect.yMaximum())
  1681. # 创建新的转换后的
  1682. transformed_rect = QgsRectangle(rect_min_x, rect_min_y, rect_max_x, rect_max_y)
  1683. iface.mapCanvas().setExtent(transformed_rect)
  1684. iface.mapCanvas().refresh()