Climb.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. """
  2. /***************************************************************************
  3. Climb
  4. begin : 2019-05-15
  5. copyright : (C) 2019 by Håvard Tveite
  6. email : havard.tveite@nmbu.no
  7. ***************************************************************************/
  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__ = 'Håvard Tveite'
  18. __date__ = '2019-03-01'
  19. __copyright__ = '(C) 2019 by Håvard Tveite'
  20. import os
  21. import math
  22. from qgis.PyQt.QtGui import QIcon
  23. from qgis.PyQt.QtCore import QVariant
  24. from qgis.core import (QgsProcessing,
  25. QgsFeatureSink,
  26. QgsProcessingAlgorithm,
  27. QgsProcessingParameterFeatureSource,
  28. QgsProcessingParameterFeatureSink,
  29. QgsProcessingOutputNumber,
  30. QgsProcessingException,
  31. QgsProcessingUtils,
  32. QgsWkbTypes,
  33. QgsFields,
  34. QgsField)
  35. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  36. class Climb(QgisAlgorithm):
  37. INPUT = 'INPUT'
  38. OUTPUT = 'OUTPUT'
  39. TOTALCLIMB = 'TOTALCLIMB'
  40. TOTALDESCENT = 'TOTALDESCENT'
  41. MINELEVATION = 'MINELEVATION'
  42. MAXELEVATION = 'MAXELEVATION'
  43. CLIMBATTRIBUTE = 'climb'
  44. DESCENTATTRIBUTE = 'descent'
  45. MINELEVATTRIBUTE = 'minelev'
  46. MAXELEVATTRIBUTE = 'maxelev'
  47. def name(self):
  48. return 'climbalongline'
  49. def displayName(self):
  50. return self.tr('Climb along line')
  51. def group(self):
  52. return self.tr('Vector analysis')
  53. def groupId(self):
  54. return 'vectoranalysis'
  55. def __init__(self):
  56. super().__init__()
  57. def initAlgorithm(self, config=None):
  58. self.addParameter(
  59. QgsProcessingParameterFeatureSource(
  60. self.INPUT,
  61. self.tr('Line layer'),
  62. [QgsProcessing.TypeVectorLine]
  63. )
  64. )
  65. self.addParameter(
  66. QgsProcessingParameterFeatureSink(
  67. self.OUTPUT,
  68. self.tr('Climb layer')
  69. )
  70. )
  71. self.addOutput(
  72. QgsProcessingOutputNumber(
  73. self.TOTALCLIMB,
  74. self.tr('Total climb')
  75. )
  76. )
  77. self.addOutput(
  78. QgsProcessingOutputNumber(
  79. self.TOTALDESCENT,
  80. self.tr('Total descent')
  81. )
  82. )
  83. self.addOutput(
  84. QgsProcessingOutputNumber(
  85. self.MINELEVATION,
  86. self.tr('Minimum elevation')
  87. )
  88. )
  89. self.addOutput(
  90. QgsProcessingOutputNumber(
  91. self.MAXELEVATION,
  92. self.tr('Maximum elevation')
  93. )
  94. )
  95. def processAlgorithm(self, parameters, context, feedback):
  96. source = self.parameterAsSource(
  97. parameters,
  98. self.INPUT,
  99. context
  100. )
  101. fcount = source.featureCount()
  102. source_fields = source.fields()
  103. hasZ = QgsWkbTypes.hasZ(source.wkbType())
  104. if not hasZ:
  105. raise QgsProcessingException(self.tr('The layer does not have Z values. If you have a DEM, use the Drape algorithm to extract Z values.'))
  106. thefields = QgsFields()
  107. climbindex = -1
  108. descentindex = -1
  109. minelevindex = -1
  110. maxelevindex = -1
  111. fieldnumber = 0
  112. # Create new fields for climb and descent
  113. thefields.append(QgsField(self.CLIMBATTRIBUTE, QVariant.Double))
  114. thefields.append(QgsField(self.DESCENTATTRIBUTE, QVariant.Double))
  115. thefields.append(QgsField(self.MINELEVATTRIBUTE, QVariant.Double))
  116. thefields.append(QgsField(self.MAXELEVATTRIBUTE, QVariant.Double))
  117. # combine all the vector fields
  118. out_fields = QgsProcessingUtils.combineFields(thefields, source_fields)
  119. layerwithz = source
  120. (sink, dest_id) = self.parameterAsSink(parameters,
  121. self.OUTPUT,
  122. context,
  123. out_fields,
  124. layerwithz.wkbType(),
  125. source.sourceCrs())
  126. # get features from source (with z values)
  127. features = layerwithz.getFeatures()
  128. totalclimb = 0
  129. totaldescent = 0
  130. minelevation = float('Infinity')
  131. maxelevation = float('-Infinity')
  132. no_z_nodes = []
  133. no_geometry = []
  134. for current, feature in enumerate(features):
  135. if feedback.isCanceled():
  136. break
  137. climb = 0
  138. descent = 0
  139. minelev = float('Infinity')
  140. maxelev = float('-Infinity')
  141. # In case of multigeometries we need to do the parts
  142. parts = feature.geometry().constParts()
  143. if not feature.hasGeometry():
  144. no_geometry.append(self.tr(
  145. 'Feature: {feature_id}'.format(
  146. feature_id=feature.id())
  147. )
  148. )
  149. for partnumber, part in enumerate(parts):
  150. # Calculate the climb
  151. first = True
  152. zval = 0
  153. for idx, v in enumerate(part.vertices()):
  154. zval = v.z()
  155. if math.isnan(zval):
  156. no_z_nodes.append(self.tr(
  157. 'Feature: {feature_id}, part: {part_id}, point: {point_id}'.format(
  158. feature_id=feature.id(),
  159. part_id=partnumber,
  160. point_id=idx)
  161. )
  162. )
  163. continue
  164. if first:
  165. prevz = zval
  166. minelev = zval
  167. maxelev = zval
  168. first = False
  169. else:
  170. diff = zval - prevz
  171. if diff > 0:
  172. climb += diff
  173. else:
  174. descent -= diff
  175. minelev = min(minelev, zval)
  176. maxelev = max(maxelev, zval)
  177. prevz = zval
  178. totalclimb += climb
  179. totaldescent += descent
  180. # Set the attribute values
  181. # Append the attributes to the end of the existing ones
  182. attrs = [
  183. climb,
  184. descent,
  185. minelev,
  186. maxelev,
  187. *feature.attributes()
  188. ]
  189. # Set the final attribute list
  190. feature.setAttributes(attrs)
  191. # Add a feature to the sink
  192. sink.addFeature(feature, QgsFeatureSink.FastInsert)
  193. minelevation = min(minelevation, minelev)
  194. maxelevation = max(maxelevation, maxelev)
  195. # Update the progress bar
  196. if fcount > 0:
  197. feedback.setProgress(int(100 * current / fcount))
  198. feedback.pushInfo(self.tr(
  199. 'The following features do not have geometry: {no_geometry_report}'.format(
  200. no_geometry_report=(', '.join(no_geometry)))
  201. )
  202. )
  203. feedback.pushInfo(self.tr(
  204. 'The following points do not have Z values: {no_z_report}'.format(
  205. no_z_report=(', '.join(no_z_nodes)))
  206. )
  207. )
  208. # Return the results
  209. return {self.OUTPUT: dest_id, self.TOTALCLIMB: totalclimb,
  210. self.TOTALDESCENT: totaldescent,
  211. self.MINELEVATION: minelevation,
  212. self.MAXELEVATION: maxelevation}