RandomPointsPolygons.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. """
  2. ***************************************************************************
  3. RandomPointsPolygons.py
  4. ---------------------
  5. Date : April 2014
  6. Copyright : (C) 2014 by Alexander Bruy
  7. Email : alexander dot bruy 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__ = 'Alexander Bruy'
  18. __date__ = 'April 2014'
  19. __copyright__ = '(C) 2014, Alexander Bruy'
  20. import os
  21. import random
  22. from qgis.PyQt.QtCore import QVariant
  23. from qgis.core import (QgsApplication,
  24. QgsField,
  25. QgsFeatureSink,
  26. QgsFeature,
  27. QgsFields,
  28. QgsGeometry,
  29. QgsPointXY,
  30. QgsWkbTypes,
  31. QgsSpatialIndex,
  32. QgsExpression,
  33. QgsDistanceArea,
  34. QgsPropertyDefinition,
  35. QgsProcessing,
  36. QgsProcessingException,
  37. QgsProcessingParameters,
  38. QgsProcessingParameterDefinition,
  39. QgsProcessingParameterNumber,
  40. QgsProcessingParameterDistance,
  41. QgsProcessingParameterFeatureSource,
  42. QgsProcessingParameterFeatureSink,
  43. QgsProcessingParameterExpression,
  44. QgsProcessingParameterEnum)
  45. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  46. from processing.tools import vector
  47. pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
  48. class RandomPointsPolygons(QgisAlgorithm):
  49. INPUT = 'INPUT'
  50. VALUE = 'VALUE'
  51. EXPRESSION = 'EXPRESSION'
  52. MIN_DISTANCE = 'MIN_DISTANCE'
  53. STRATEGY = 'STRATEGY'
  54. OUTPUT = 'OUTPUT'
  55. def icon(self):
  56. return QgsApplication.getThemeIcon("/algorithms/mAlgorithmRandomPointsWithinPolygon.svg")
  57. def svgIconPath(self):
  58. return QgsApplication.iconPath("/algorithms/mAlgorithmRandomPointsWithinPolygon.svg")
  59. def group(self):
  60. return self.tr('Vector creation')
  61. def groupId(self):
  62. return 'vectorcreation'
  63. def __init__(self):
  64. super().__init__()
  65. def initAlgorithm(self, config=None):
  66. self.strategies = [self.tr('Points count'),
  67. self.tr('Points density')]
  68. self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
  69. self.tr('Input layer'),
  70. [QgsProcessing.TypeVectorPolygon]))
  71. self.addParameter(QgsProcessingParameterEnum(self.STRATEGY,
  72. self.tr('Sampling strategy'),
  73. self.strategies,
  74. False,
  75. 0))
  76. value_param = QgsProcessingParameterNumber(self.VALUE,
  77. self.tr('Point count or density'),
  78. QgsProcessingParameterNumber.Double,
  79. 1,
  80. minValue=0)
  81. value_param.setIsDynamic(True)
  82. value_param.setDynamicLayerParameterName(self.INPUT)
  83. value_param.setDynamicPropertyDefinition(
  84. QgsPropertyDefinition("Value", self.tr("Point count or density"), QgsPropertyDefinition.Double))
  85. self.addParameter(value_param)
  86. # deprecated expression parameter - overrides value parameter if set
  87. exp_param = QgsProcessingParameterExpression(self.EXPRESSION,
  88. self.tr('Expression'), optional=True,
  89. parentLayerParameterName=self.INPUT)
  90. exp_param.setFlags(exp_param.flags() | QgsProcessingParameterDefinition.FlagHidden)
  91. self.addParameter(exp_param)
  92. self.addParameter(QgsProcessingParameterDistance(self.MIN_DISTANCE,
  93. self.tr('Minimum distance between points'),
  94. None, self.INPUT, True, 0, 1000000000))
  95. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
  96. self.tr('Random points'),
  97. type=QgsProcessing.TypeVectorPoint))
  98. def name(self):
  99. return 'randompointsinsidepolygons'
  100. def displayName(self):
  101. return self.tr('Random points inside polygons')
  102. def processAlgorithm(self, parameters, context, feedback):
  103. source = self.parameterAsSource(parameters, self.INPUT, context)
  104. if source is None:
  105. raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
  106. strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
  107. if self.MIN_DISTANCE in parameters and parameters[self.MIN_DISTANCE] is not None:
  108. minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)
  109. else:
  110. minDistance = None
  111. expressionContext = self.createExpressionContext(parameters, context, source)
  112. dynamic_value = QgsProcessingParameters.isDynamic(parameters, "VALUE")
  113. value_property = None
  114. if self.EXPRESSION in parameters and parameters[self.EXPRESSION] is not None:
  115. expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context))
  116. value = None
  117. if expression.hasParserError():
  118. raise QgsProcessingException(expression.parserErrorString())
  119. expression.prepare(expressionContext)
  120. else:
  121. expression = None
  122. if dynamic_value:
  123. value_property = parameters["VALUE"]
  124. value = self.parameterAsDouble(parameters, self.VALUE, context)
  125. fields = QgsFields()
  126. fields.append(QgsField('id', QVariant.Int, '', 10, 0))
  127. (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
  128. fields, QgsWkbTypes.Point, source.sourceCrs(),
  129. QgsFeatureSink.RegeneratePrimaryKey)
  130. if sink is None:
  131. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  132. da = QgsDistanceArea()
  133. da.setSourceCrs(source.sourceCrs(), context.transformContext())
  134. da.setEllipsoid(context.ellipsoid())
  135. total = 100.0 / source.featureCount() if source.featureCount() else 0
  136. current_progress = 0
  137. pointId = 0
  138. for current, f in enumerate(source.getFeatures()):
  139. if feedback.isCanceled():
  140. break
  141. if not f.hasGeometry():
  142. continue
  143. current_progress = total * current
  144. feedback.setProgress(current_progress)
  145. this_value = value
  146. if value_property is not None or expression is not None:
  147. expressionContext.setFeature(f)
  148. if value_property:
  149. this_value, _ = value_property.valueAsDouble(expressionContext, value)
  150. else:
  151. this_value = expression.evaluate(expressionContext)
  152. if expression.hasEvalError():
  153. feedback.pushInfo(
  154. self.tr('Evaluation error for feature ID {}: {}').format(f.id(), expression.evalErrorString()))
  155. continue
  156. fGeom = f.geometry()
  157. engine = QgsGeometry.createGeometryEngine(fGeom.constGet())
  158. engine.prepareGeometry()
  159. bbox = fGeom.boundingBox()
  160. if strategy == 0:
  161. pointCount = int(this_value)
  162. else:
  163. pointCount = int(round(this_value * da.measureArea(fGeom)))
  164. if pointCount == 0:
  165. feedback.pushInfo(self.tr("Skip feature {} as number of points for it is 0.").format(f.id()))
  166. continue
  167. index = None
  168. if minDistance:
  169. index = QgsSpatialIndex()
  170. points = {}
  171. nPoints = 0
  172. nIterations = 0
  173. maxIterations = pointCount * 200
  174. feature_total = total / pointCount if pointCount else 1
  175. random.seed()
  176. while nIterations < maxIterations and nPoints < pointCount:
  177. if feedback.isCanceled():
  178. break
  179. rx = bbox.xMinimum() + bbox.width() * random.random()
  180. ry = bbox.yMinimum() + bbox.height() * random.random()
  181. p = QgsPointXY(rx, ry)
  182. geom = QgsGeometry.fromPointXY(p)
  183. if engine.contains(geom.constGet()) and \
  184. (not minDistance or vector.checkMinDistance(p, index, minDistance, points)):
  185. f = QgsFeature(nPoints)
  186. f.initAttributes(1)
  187. f.setFields(fields)
  188. f.setAttribute('id', pointId)
  189. f.setGeometry(geom)
  190. sink.addFeature(f, QgsFeatureSink.FastInsert)
  191. if minDistance:
  192. index.addFeature(f)
  193. points[nPoints] = p
  194. nPoints += 1
  195. pointId += 1
  196. feedback.setProgress(current_progress + int(nPoints * feature_total))
  197. nIterations += 1
  198. if nPoints < pointCount:
  199. feedback.pushInfo(self.tr('Could not generate requested number of random '
  200. 'points. Maximum number of attempts exceeded.'))
  201. feedback.setProgress(100)
  202. return {self.OUTPUT: dest_id}