searchDock.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import re
  2. from qgis.PyQt import QtWidgets
  3. from qgis.PyQt.QtCore import QThread, pyqtSignal
  4. from qgis.PyQt.QtWidgets import QTreeWidget, QTreeWidgetItem
  5. from qgis.core import Qgis
  6. from qgis.core import (
  7. QgsFeature,
  8. QgsProject,
  9. QgsSettings,
  10. QgsVectorLayer,
  11. QgsPointXY,
  12. QgsGeometry,
  13. QgsCoordinateReferenceSystem,
  14. QgsCoordinateTransform,
  15. )
  16. from ...ui.search import Ui_SearchDockWidget
  17. from ...utils import PluginDir, TiandituAPI
  18. class SearchRequestThread(QThread):
  19. request_finished = pyqtSignal(dict)
  20. def __init__(self, search_type, api, data):
  21. super().__init__()
  22. self.search_type = search_type # 搜索类型
  23. self.data = data # 搜索参数
  24. self.api = api
  25. def handle_response_api_search_v2(self, response):
  26. if response["code"] == 1:
  27. data = response["data"]
  28. if data["status"]["infocode"] != 0:
  29. # 直接返回POI的情况
  30. if data["resultType"] == 1:
  31. pois = data["pois"]
  32. # 获取当前搜索结果所在的行政区,作为根节点
  33. if "prompt" in data: # prompt不一定存在
  34. admins = data["prompt"][0]["admins"][0]["adminName"]
  35. else:
  36. admins = "全国"
  37. self.request_finished.emit(
  38. {"type": "api_search_v2:1", "admins": admins, "pois": pois}
  39. )
  40. # 返回统计集合的情况
  41. elif data["resultType"] == 2:
  42. all_admins = data["statistics"]["allAdmins"]
  43. first = all_admins[0]
  44. if not first["isleaf"]:
  45. # 地点不精确的时候,返回的统计集合为省级。不往下继续搜索了
  46. self.request_finished.emit(
  47. {"type": "no_result", "message": "请输入更为详细的地名"}
  48. )
  49. else:
  50. self.request_finished.emit(
  51. {"type": "api_search_v2:2", "all_admins": all_admins}
  52. )
  53. else:
  54. self.request_finished.emit({"type": "no_result", "message": "无结果"})
  55. else:
  56. self.request_finished.emit(
  57. {"type": "error", "message": response["message"]}
  58. )
  59. def handle_response_api_search_v2_admincode(self, response):
  60. if response["code"] == 1:
  61. data = response["data"]
  62. if data["resultType"] == 1:
  63. pois = data["pois"]
  64. self.request_finished.emit(
  65. {"type": "api_search_v2_admincode", "pois": pois}
  66. )
  67. def handle_response_api_geocoder(self, response):
  68. if response["code"] == 1:
  69. data = response["data"]
  70. if data["msg"] == "ok":
  71. location = data["location"]
  72. level = location["level"]
  73. score = location["score"]
  74. lon = round(float(location["lon"]), 6)
  75. lat = round(float(location["lat"]), 6)
  76. t = f"<p>关键词: {location['keyWord']}</p><p>Score:{score}</p><p>类别名称: {level}</p>"
  77. _link = '<a href="#">添加到地图中</a>'
  78. t += f"经纬度: {lon},{lat} {_link} "
  79. else:
  80. t = "请求失败"
  81. self.request_finished.emit({"text": "请求失败!"})
  82. else:
  83. t = f"请求失败!{response['message']}"
  84. self.request_finished.emit({"type": "api_geocoder", "text": t})
  85. def handle_response_api_regeocoder(self, response):
  86. if response["code"] == 1:
  87. data = response["data"]
  88. if data["status"] == "0":
  89. result = data["result"]
  90. formatted_address = result["formatted_address"]
  91. if formatted_address != "":
  92. text = formatted_address
  93. else:
  94. text = "无结果"
  95. else:
  96. text = "请求失败"
  97. else:
  98. text = f"请求失败!{response['message']}"
  99. self.request_finished.emit({"type": "api_regeocoder", "text": text})
  100. def run(self):
  101. if self.search_type == "api_search_v2":
  102. keyword = self.data["keyword"]
  103. r = self.api.api_search_v2(keyword)
  104. self.handle_response_api_search_v2(r)
  105. elif self.search_type == "api_search_v2_admincode":
  106. keyword = self.data["keyword"]
  107. admin_code = self.data["admin_code"]
  108. r = self.api.api_search_v2(keyword, specify=admin_code)
  109. self.handle_response_api_search_v2_admincode(r)
  110. elif self.search_type == "api_geocoder":
  111. keyword = self.data["keyword"]
  112. r = self.api.api_geocoder(keyword)
  113. self.handle_response_api_geocoder(r)
  114. elif self.search_type == "api_regeocoder":
  115. lon = self.data["lon"]
  116. lat = self.data["lat"]
  117. r = self.api.api_regeocoder(lon, lat)
  118. self.handle_response_api_regeocoder(r)
  119. def create_point_layer(name: str, point: QgsPointXY, crs: str):
  120. layer = QgsVectorLayer(f"Point?crs={crs}&field=Name:string", name, "memory")
  121. pr = layer.dataProvider()
  122. point_feature = QgsFeature()
  123. point_feature.setGeometry(QgsGeometry.fromPointXY(point))
  124. point_feature.setAttributes([name])
  125. pr.addFeature(point_feature)
  126. return layer
  127. class SearchDockWidget(QtWidgets.QDockWidget, Ui_SearchDockWidget):
  128. def __init__(self, iface):
  129. super().__init__()
  130. self.search_request_thread = None
  131. self.setupUi(self)
  132. self.iface = iface
  133. self.qset = QgsSettings()
  134. # 读取token
  135. self.token = self.qset.value("tianditu-tools/Tianditu/key")
  136. self.api = TiandituAPI(self.token)
  137. # 初始化treeWidget
  138. # self.treeWidget = QTreeWidget(self.tab)
  139. # self.treeWidget.setObjectName("treeWidget")
  140. # self.treeWidget.setColumnCount(4)
  141. # self.treeWidget.setHeaderLabels(["行政区", "地点", "lonlat", "admin_code"])
  142. # self.treeWidget.setColumnHidden(2, True)
  143. # self.treeWidget.setColumnHidden(3, True)
  144. # self.treeWidget.setAlternatingRowColors(True)
  145. # self.verticalLayout_2.addWidget(self.treeWidget)
  146. # 地名搜索
  147. # self.pushButton.clicked.connect(self.search)
  148. # 地理编码查询
  149. # self.pushButton_2.clicked.connect(self.geocoder)
  150. # self.label_2.linkActivated.connect(self.geocoder_result_link_clicked)
  151. # 逆地理编码查询
  152. # self.pushButton_3.clicked.connect(self.regeocoder)
  153. def on_treeWidget_item_double_clicked(self, item, _):
  154. # 没有子节点的根节点,根据根节点的行政区划进行搜索,行政区划代码在第4列(index=3)
  155. if item.childCount() == 0:
  156. if item.parent() is None:
  157. admin_code = item.text(3)
  158. keyword = self.lineEdit.text()
  159. search_progress_tip_item = QTreeWidgetItem(item)
  160. search_progress_tip_item.setText(1, "搜索中...")
  161. self.search_request_thread = SearchRequestThread(
  162. search_type="api_search_v2_admincode",
  163. api=self.api,
  164. data={"keyword": keyword, "admin_code": admin_code},
  165. )
  166. self.search_request_thread.request_finished.connect(
  167. lambda data: self.on_search_complete(data, item)
  168. )
  169. self.search_request_thread.start()
  170. else:
  171. name = item.text(1)
  172. lonlat = item.text(2)
  173. lon, lat = map(float, lonlat.split(","))
  174. self.addPoint(name, lon, lat)
  175. def addPoint(self, name, x, y):
  176. # 创建一个图层组,用于存放地名搜索结果
  177. root = QgsProject.instance().layerTreeRoot()
  178. group_name = "地名搜索结果"
  179. group = root.findGroup(group_name)
  180. if group is None:
  181. group = root.addGroup(group_name)
  182. # 定义图层
  183. raw_point = QgsPointXY(x, y)
  184. # 当前工程坐标系
  185. current_project_crs = QgsProject.instance().crs()
  186. # 定义坐标转换
  187. coord_trans = QgsCoordinateTransform(
  188. QgsCoordinateReferenceSystem("EPSG:4326"),
  189. current_project_crs,
  190. QgsProject.instance(),
  191. )
  192. projected_point = coord_trans.transform(raw_point)
  193. raw_layer = create_point_layer(name, raw_point, "EPSG:4326")
  194. # 此图层用于缩放到点
  195. layer = create_point_layer(name, projected_point, current_project_crs.authid())
  196. group.addLayer(raw_layer)
  197. # 加载图层样式
  198. # 根据QGIS版本设置不同的样式
  199. current_qgis_version = Qgis.QGIS_VERSION_INT
  200. if current_qgis_version <= 31616:
  201. raw_layer.loadNamedStyle(
  202. str(PluginDir.joinpath("./Styles/PointStyle_316.qml"))
  203. )
  204. else:
  205. raw_layer.loadNamedStyle(str(PluginDir.joinpath("./Styles/PointStyle.qml")))
  206. raw_layer.updateExtents()
  207. QgsProject.instance().addMapLayer(raw_layer, False)
  208. # 画布缩放到点
  209. rect = layer.extent()
  210. self.iface.mapCanvas().setExtent(rect)
  211. self.iface.mapCanvas().zoomScale(18056) # 设置缩放等级, setExtent的缩放等级太大
  212. self.iface.mapCanvas().refresh()
  213. def on_search_complete(self, data, item=None):
  214. search_type = data["type"]
  215. if search_type == "api_search_v2:1":
  216. self.treeWidget.takeTopLevelItem(0) # 移除"搜索中..."
  217. admins = data["admins"]
  218. pois = data["pois"]
  219. root = QTreeWidgetItem(self.treeWidget)
  220. root.setText(0, admins)
  221. for index, poi in enumerate(pois):
  222. child = QTreeWidgetItem(root)
  223. child.setText(0, f"{index + 1}")
  224. child.setText(1, poi["name"])
  225. child.setText(2, poi["lonlat"])
  226. # 展开所有节点
  227. self.treeWidget.expandAll()
  228. # item双击信号
  229. self.treeWidget.itemDoubleClicked.connect(
  230. self.on_treeWidget_item_double_clicked
  231. )
  232. elif search_type == "api_search_v2:2":
  233. self.treeWidget.takeTopLevelItem(0) # 移除"搜索中..."
  234. all_admins = data["all_admins"]
  235. for index, admins in enumerate(all_admins):
  236. root = QTreeWidgetItem(self.treeWidget)
  237. root.setText(0, f"{index + 1} {admins['adminName']}")
  238. root.setText(1, f"{admins['count']}个结果")
  239. root.setText(3, f"{admins['adminCode']}")
  240. self.treeWidget.itemDoubleClicked.connect(
  241. self.on_treeWidget_item_double_clicked
  242. )
  243. elif search_type == "api_search_v2_admincode":
  244. pois = data["pois"]
  245. for index, poi in enumerate(pois):
  246. child = QTreeWidgetItem(item)
  247. child.setText(0, str(index + 1))
  248. child.setText(1, poi["name"])
  249. child.setText(2, poi["lonlat"])
  250. item.removeChild(item.child(0))
  251. elif search_type == "api_geocoder":
  252. text = data["text"]
  253. self.label_2.setText(text)
  254. elif search_type == "api_regeocoder":
  255. text = data["text"]
  256. self.label_4.setText(text)
  257. elif search_type == "no_result":
  258. self.treeWidget.takeTopLevelItem(0) # 移除"搜索中..."
  259. root = QTreeWidgetItem(self.treeWidget)
  260. root.setText(1, data["message"])
  261. elif search_type == "error":
  262. self.treeWidget.takeTopLevelItem(0) # 移除"搜索中..."
  263. root = QTreeWidgetItem(self.treeWidget)
  264. root.setText(1, data["message"])
  265. self.iface.messageBar().pushWarning(
  266. title="天地图API - Error", message=data["message"]
  267. )
  268. else:
  269. self.treeWidget.takeTopLevelItem(0) # 移除"搜索中..."
  270. root = QTreeWidgetItem(self.treeWidget)
  271. root.setText(0, "无结果")
  272. def search(self):
  273. keyword = self.lineEdit.text()
  274. if len(keyword) == 0:
  275. return
  276. # 清除treeWidget数据
  277. self.treeWidget.clear()
  278. # 检查信号是否已经连接,连接的话就断开
  279. if self.treeWidget.receivers(self.treeWidget.itemDoubleClicked) > 0:
  280. self.treeWidget.itemDoubleClicked.disconnect(
  281. self.on_treeWidget_item_double_clicked
  282. )
  283. # 搜索
  284. search_progress_tip = QTreeWidgetItem(self.treeWidget)
  285. search_progress_tip.setText(1, "搜索中...")
  286. self.search_request_thread = SearchRequestThread(
  287. search_type="api_search_v2", api=self.api, data={"keyword": keyword}
  288. )
  289. self.search_request_thread.request_finished.connect(self.on_search_complete)
  290. self.search_request_thread.start()
  291. def geocoder(self):
  292. keyword = self.lineEdit_2.text()
  293. if len(keyword) == 0:
  294. return
  295. self.label_2.setText("搜索中...")
  296. self.search_request_thread = SearchRequestThread(
  297. search_type="api_geocoder", api=self.api, data={"keyword": keyword}
  298. )
  299. self.search_request_thread.request_finished.connect(self.on_search_complete)
  300. self.search_request_thread.start()
  301. def geocoder_result_link_clicked(self):
  302. text = self.label_2.text()
  303. name = text.split("关键词:")[1].split("<")[0].strip()
  304. pattern = r"经纬度: ([\d\.]+),([\d\.]+)"
  305. match = re.search(pattern, text)
  306. # 如果匹配成功,则提取经纬度信息
  307. if match:
  308. longitude = float(match.group(1))
  309. latitude = float(match.group(2))
  310. self.addPoint(name, longitude, latitude)
  311. else:
  312. self.iface.messageBar().pushWarning(
  313. title="天地图API - Error: ", message="添加地图点失败"
  314. )
  315. def regeocoder(self):
  316. lonlat = self.lineEdit_3.text()
  317. if len(lonlat) == 0:
  318. return
  319. try:
  320. lon, lat = map(float, lonlat.split(","))
  321. self.label_4.setText("搜索中...")
  322. self.search_request_thread = SearchRequestThread(
  323. search_type="api_regeocoder",
  324. api=self.api,
  325. data={"lon": lon, "lat": lat},
  326. )
  327. self.search_request_thread.request_finished.connect(self.on_search_complete)
  328. self.search_request_thread.start()
  329. except ValueError as e:
  330. self.iface.messageBar().pushWarning(
  331. title="天地图API - Error: 经纬度输入有误", message=str(e)
  332. )