Postprocessing.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. """
  2. ***************************************************************************
  3. Postprocessing.py
  4. ---------------------
  5. Date : August 2012
  6. Copyright : (C) 2012 by Victor Olaya
  7. Email : volayaf at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Victor Olaya'
  18. __date__ = 'August 2012'
  19. __copyright__ = '(C) 2012, Victor Olaya'
  20. import os
  21. import traceback
  22. from typing import (
  23. Dict,
  24. List,
  25. Optional,
  26. Tuple
  27. )
  28. from qgis.PyQt.QtCore import QCoreApplication
  29. from qgis.core import (
  30. Qgis,
  31. QgsProcessingFeedback,
  32. QgsProcessingUtils,
  33. QgsMapLayer,
  34. QgsWkbTypes,
  35. QgsMessageLog,
  36. QgsProcessingContext,
  37. QgsProcessingAlgorithm,
  38. QgsLayerTreeLayer,
  39. QgsLayerTreeGroup
  40. )
  41. from processing.core.ProcessingConfig import ProcessingConfig
  42. from processing.gui.RenderingStyles import RenderingStyles
  43. SORT_ORDER_CUSTOM_PROPERTY = '_processing_sort_order'
  44. def determine_output_name(dest_id: str,
  45. details: QgsProcessingContext.LayerDetails,
  46. alg: QgsProcessingAlgorithm,
  47. context: QgsProcessingContext,
  48. parameters: Dict) -> str:
  49. """
  50. If running a model, the execution will arrive here when an
  51. algorithm that is part of that model is executed. We check if
  52. its output is a final output of the model, and adapt the output
  53. name accordingly
  54. """
  55. for out in alg.outputDefinitions():
  56. if out.name() not in parameters:
  57. continue
  58. output_value = parameters[out.name()]
  59. if hasattr(output_value, "sink"):
  60. output_value = output_value.sink.valueAsString(
  61. context.expressionContext()
  62. )[0]
  63. else:
  64. output_value = str(output_value)
  65. if output_value == dest_id:
  66. return out.name()
  67. return details.outputName
  68. def post_process_layer(output_name: str,
  69. layer: QgsMapLayer,
  70. alg: QgsProcessingAlgorithm):
  71. """
  72. Applies post-processing steps to a layer
  73. """
  74. style = None
  75. if output_name:
  76. style = RenderingStyles.getStyle(alg.id(), output_name)
  77. if style is None:
  78. if layer.type() == Qgis.LayerType.Raster:
  79. style = ProcessingConfig.getSetting(ProcessingConfig.RASTER_STYLE)
  80. elif layer.type() == Qgis.LayerType.Vector:
  81. if layer.geometryType() == QgsWkbTypes.PointGeometry:
  82. style = ProcessingConfig.getSetting(
  83. ProcessingConfig.VECTOR_POINT_STYLE)
  84. elif layer.geometryType() == QgsWkbTypes.LineGeometry:
  85. style = ProcessingConfig.getSetting(
  86. ProcessingConfig.VECTOR_LINE_STYLE)
  87. else:
  88. style = ProcessingConfig.getSetting(
  89. ProcessingConfig.VECTOR_POLYGON_STYLE)
  90. if style:
  91. layer.loadNamedStyle(style)
  92. try:
  93. from qgis._3d import QgsPointCloudLayer3DRenderer
  94. if layer.type() == Qgis.LayerType.PointCloud:
  95. if layer.renderer3D() is None:
  96. # If the layer has no 3D renderer and syncing 3D to 2D
  97. # renderer is enabled, we create a renderer and set it up
  98. # with the 2D renderer
  99. if layer.sync3DRendererTo2DRenderer():
  100. renderer_3d = QgsPointCloudLayer3DRenderer()
  101. renderer_3d.convertFrom2DRenderer(layer.renderer())
  102. layer.setRenderer3D(renderer_3d)
  103. except ImportError:
  104. QgsMessageLog.logMessage(
  105. QCoreApplication.translate(
  106. "Postprocessing",
  107. "3D library is not available, "
  108. "can't assign a 3d renderer to a layer."
  109. )
  110. )
  111. def create_layer_tree_layer(layer: QgsMapLayer,
  112. details: QgsProcessingContext.LayerDetails) \
  113. -> QgsLayerTreeLayer:
  114. """
  115. Applies post-processing steps to a QgsLayerTreeLayer created for
  116. an algorithm's output
  117. """
  118. layer_tree_layer = QgsLayerTreeLayer(layer)
  119. if ProcessingConfig.getSetting(ProcessingConfig.VECTOR_FEATURE_COUNT) and \
  120. layer.type() == Qgis.LayerType.Vector:
  121. layer_tree_layer.setCustomProperty("showFeatureCount", True)
  122. if details.layerSortKey:
  123. layer_tree_layer.setCustomProperty(SORT_ORDER_CUSTOM_PROPERTY,
  124. details.layerSortKey)
  125. return layer_tree_layer
  126. def get_layer_tree_results_group(details: QgsProcessingContext.LayerDetails,
  127. context: QgsProcessingContext) \
  128. -> QgsLayerTreeGroup:
  129. """
  130. Returns the destination layer tree group to store results in
  131. """
  132. destination_project = details.project or context.project()
  133. # default to placing results in the top level of the layer tree
  134. results_group = details.project.layerTreeRoot()
  135. # if a specific results group is specified in Processing settings,
  136. # respect it (and create if necessary)
  137. results_group_name = ProcessingConfig.getSetting(
  138. ProcessingConfig.RESULTS_GROUP_NAME)
  139. if results_group_name:
  140. results_group = destination_project.layerTreeRoot().findGroup(
  141. results_group_name)
  142. if not results_group:
  143. results_group = destination_project.layerTreeRoot().insertGroup(
  144. 0, results_group_name)
  145. results_group.setExpanded(True)
  146. # if this particular output layer has a specific output group assigned,
  147. # find or create it now
  148. if details.groupName:
  149. group = results_group.findGroup(details.groupName)
  150. if not group:
  151. group = results_group.insertGroup(
  152. 0, details.groupName)
  153. group.setExpanded(True)
  154. else:
  155. group = results_group
  156. return group
  157. def handleAlgorithmResults(alg: QgsProcessingAlgorithm,
  158. context: QgsProcessingContext,
  159. feedback: Optional[QgsProcessingFeedback] = None,
  160. parameters: Optional[Dict] = None):
  161. if not parameters:
  162. parameters = {}
  163. if feedback is None:
  164. feedback = QgsProcessingFeedback()
  165. wrong_layers = []
  166. feedback.setProgressText(
  167. QCoreApplication.translate(
  168. 'Postprocessing',
  169. 'Loading resulting layers'
  170. )
  171. )
  172. i = 0
  173. added_layers: List[Tuple[QgsLayerTreeGroup, QgsLayerTreeLayer]] = []
  174. layers_to_post_process: List[Tuple[QgsMapLayer,
  175. QgsProcessingContext.LayerDetails]] = []
  176. for dest_id, details in context.layersToLoadOnCompletion().items():
  177. if feedback.isCanceled():
  178. return False
  179. if len(context.layersToLoadOnCompletion()) > 2:
  180. # only show progress feedback if we're loading a bunch of layers
  181. feedback.setProgress(
  182. 100 * i / float(len(context.layersToLoadOnCompletion()))
  183. )
  184. try:
  185. print(f"开始加载输出图层位置:{dest_id}")
  186. if os.path.exists(dest_id) and os.path.isfile(dest_id):
  187. layer = QgsProcessingUtils.mapLayerFromString(
  188. dest_id,
  189. context,
  190. typeHint=details.layerTypeHint
  191. )
  192. if layer is not None:
  193. details.setOutputLayerName(layer)
  194. output_name = determine_output_name(
  195. dest_id, details, alg, context, parameters
  196. )
  197. post_process_layer(output_name, layer, alg)
  198. # Load layer to layer tree root or to a specific group
  199. results_group = get_layer_tree_results_group(details, context)
  200. # note here that we may not retrieve an owned layer -- eg if the
  201. # output layer already exists in the destination project
  202. owned_map_layer = context.temporaryLayerStore().takeMapLayer(layer)
  203. if owned_map_layer:
  204. details.project.addMapLayer(owned_map_layer, False)
  205. # we don't add the layer to the tree yet -- that's done
  206. # later, after we've sorted all added layers
  207. layer_tree_layer = create_layer_tree_layer(owned_map_layer, details)
  208. added_layers.append((results_group, layer_tree_layer))
  209. if details.postProcessor():
  210. # we defer calling the postProcessor set in the context
  211. # until the layer has been added to the project's layer
  212. # tree, just in case the postProcessor contains logic
  213. # relating to layer tree handling
  214. layers_to_post_process.append((layer, details))
  215. else:
  216. wrong_layers.append(str(dest_id))
  217. else:
  218. print(f"输出图层位置不存在:{dest_id}")
  219. except Exception:
  220. QgsMessageLog.logMessage(
  221. QCoreApplication.translate(
  222. 'Postprocessing',
  223. "Error loading result layer:"
  224. ) + "\n" + traceback.format_exc(),
  225. 'Processing',
  226. Qgis.Critical)
  227. wrong_layers.append(str(dest_id))
  228. i += 1
  229. # sort added layer tree layers
  230. sorted_layer_tree_layers = sorted(
  231. added_layers,
  232. key=lambda x: x[1].customProperty(SORT_ORDER_CUSTOM_PROPERTY, 0)
  233. )
  234. for group, layer_node in sorted_layer_tree_layers:
  235. layer_node.removeCustomProperty(SORT_ORDER_CUSTOM_PROPERTY)
  236. group.insertChildNode(0, layer_node)
  237. # all layers have been added to the layer tree, so safe to call
  238. # postProcessors now
  239. for layer, details in layers_to_post_process:
  240. details.postProcessor().postProcessLayer(
  241. layer,
  242. context,
  243. feedback)
  244. feedback.setProgress(100)
  245. if wrong_layers:
  246. msg = QCoreApplication.translate(
  247. 'Postprocessing',
  248. "The following layers were not correctly generated."
  249. )
  250. msg += "\n" + "\n".join([f"• {lay}" for lay in wrong_layers]) + '\n'
  251. msg += QCoreApplication.translate(
  252. 'Postprocessing',
  253. "You can check the 'Log Messages Panel' in QGIS main window "
  254. "to find more information about the execution of the algorithm.")
  255. feedback.reportError(msg)
  256. return len(wrong_layers) == 0