RandomPointsAlongLines.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """
  2. ***************************************************************************
  3. RandomPointsAlongLines.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 random
  21. from qgis.PyQt.QtCore import QVariant
  22. from qgis.core import (QgsField,
  23. QgsFeatureSink,
  24. QgsFeature,
  25. QgsFields,
  26. QgsGeometry,
  27. QgsPointXY,
  28. QgsWkbTypes,
  29. QgsSpatialIndex,
  30. QgsFeatureRequest,
  31. QgsDistanceArea,
  32. QgsProject,
  33. QgsProcessing,
  34. QgsProcessingException,
  35. QgsProcessingParameterDistance,
  36. QgsProcessingParameterNumber,
  37. QgsProcessingParameterFeatureSource,
  38. QgsProcessingParameterFeatureSink,
  39. QgsProcessingParameterDefinition)
  40. from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
  41. from processing.tools import vector
  42. class RandomPointsAlongLines(QgisAlgorithm):
  43. INPUT = 'INPUT'
  44. POINTS_NUMBER = 'POINTS_NUMBER'
  45. MIN_DISTANCE = 'MIN_DISTANCE'
  46. OUTPUT = 'OUTPUT'
  47. def group(self):
  48. return self.tr('Vector creation')
  49. def groupId(self):
  50. return 'vectorcreation'
  51. def __init__(self):
  52. super().__init__()
  53. def initAlgorithm(self, config=None):
  54. self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
  55. self.tr('Input layer'),
  56. [QgsProcessing.TypeVectorLine]))
  57. self.addParameter(QgsProcessingParameterNumber(self.POINTS_NUMBER,
  58. self.tr('Number of points'),
  59. QgsProcessingParameterNumber.Integer,
  60. 1, False, 1, 1000000000))
  61. self.addParameter(QgsProcessingParameterDistance(self.MIN_DISTANCE,
  62. self.tr('Minimum distance between points'),
  63. 0, self.INPUT, False, 0, 1000000000))
  64. self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
  65. self.tr('Random points'),
  66. type=QgsProcessing.TypeVectorPoint))
  67. def name(self):
  68. return 'randompointsalongline'
  69. def displayName(self):
  70. return self.tr('Random points along line')
  71. def processAlgorithm(self, parameters, context, feedback):
  72. source = self.parameterAsSource(parameters, self.INPUT, context)
  73. if source is None:
  74. raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
  75. pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
  76. minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)
  77. fields = QgsFields()
  78. fields.append(QgsField('id', QVariant.Int, '', 10, 0))
  79. (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
  80. fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey)
  81. if sink is None:
  82. raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
  83. nPoints = 0
  84. nIterations = 0
  85. maxIterations = pointCount * 200
  86. total = 100.0 / pointCount if pointCount else 1
  87. index = QgsSpatialIndex()
  88. points = {}
  89. da = QgsDistanceArea()
  90. da.setSourceCrs(source.sourceCrs(), context.transformContext())
  91. da.setEllipsoid(context.ellipsoid())
  92. request = QgsFeatureRequest()
  93. random.seed()
  94. ids = source.allFeatureIds()
  95. while ids and nIterations < maxIterations and nPoints < pointCount:
  96. if feedback.isCanceled():
  97. break
  98. # pick random feature
  99. fid = random.choice(ids)
  100. try:
  101. f = next(source.getFeatures(request.setFilterFid(fid).setSubsetOfAttributes([])))
  102. except StopIteration:
  103. ids.remove(fid)
  104. continue
  105. fGeom = f.geometry()
  106. if fGeom.isEmpty():
  107. ids.remove(fid)
  108. continue
  109. if fGeom.isMultipart():
  110. lines = fGeom.asMultiPolyline()
  111. # pick random line
  112. lineId = random.randint(0, len(lines) - 1)
  113. vertices = lines[lineId]
  114. else:
  115. vertices = fGeom.asPolyline()
  116. # pick random segment
  117. nVertices = len(vertices)
  118. if nVertices < 2:
  119. nIterations += 1
  120. continue
  121. if nVertices == 2:
  122. vid = 0
  123. else:
  124. vid = random.randint(0, nVertices - 2)
  125. startPoint = vertices[vid]
  126. endPoint = vertices[vid + 1]
  127. length = da.measureLine(startPoint, endPoint)
  128. if length == 0:
  129. nIterations += 1
  130. continue
  131. dist = length * random.random()
  132. d = dist / (length - dist)
  133. rx = (startPoint.x() + d * endPoint.x()) / (1 + d)
  134. ry = (startPoint.y() + d * endPoint.y()) / (1 + d)
  135. # generate random point
  136. p = QgsPointXY(rx, ry)
  137. geom = QgsGeometry.fromPointXY(p)
  138. if vector.checkMinDistance(p, index, minDistance, points):
  139. f = QgsFeature(nPoints)
  140. f.initAttributes(1)
  141. f.setFields(fields)
  142. f.setAttribute('id', nPoints)
  143. f.setGeometry(geom)
  144. sink.addFeature(f, QgsFeatureSink.FastInsert)
  145. index.addFeature(f)
  146. points[nPoints] = p
  147. nPoints += 1
  148. feedback.setProgress(int(nPoints * total))
  149. nIterations += 1
  150. if nPoints < pointCount:
  151. feedback.pushInfo(self.tr('Could not generate requested number of random points. '
  152. 'Maximum number of attempts exceeded.'))
  153. return {self.OUTPUT: dest_id}