1# -*- coding: utf-8 -*- 2 3""" 4*************************************************************************** 5 RandomPointsAlongLines.py 6 --------------------- 7 Date : April 2014 8 Copyright : (C) 2014 by Alexander Bruy 9 Email : alexander dot bruy at gmail dot com 10*************************************************************************** 11* * 12* This program is free software; you can redistribute it and/or modify * 13* it under the terms of the GNU General Public License as published by * 14* the Free Software Foundation; either version 2 of the License, or * 15* (at your option) any later version. * 16* * 17*************************************************************************** 18""" 19 20__author__ = 'Alexander Bruy' 21__date__ = 'April 2014' 22__copyright__ = '(C) 2014, Alexander Bruy' 23 24import random 25 26from qgis.PyQt.QtCore import QVariant 27from qgis.core import (QgsField, 28 QgsFeatureSink, 29 QgsFeature, 30 QgsFields, 31 QgsGeometry, 32 QgsPointXY, 33 QgsWkbTypes, 34 QgsSpatialIndex, 35 QgsFeatureRequest, 36 QgsDistanceArea, 37 QgsProject, 38 QgsProcessing, 39 QgsProcessingException, 40 QgsProcessingParameterDistance, 41 QgsProcessingParameterNumber, 42 QgsProcessingParameterFeatureSource, 43 QgsProcessingParameterFeatureSink, 44 QgsProcessingParameterDefinition) 45 46from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm 47from processing.tools import vector 48 49 50class RandomPointsAlongLines(QgisAlgorithm): 51 INPUT = 'INPUT' 52 POINTS_NUMBER = 'POINTS_NUMBER' 53 MIN_DISTANCE = 'MIN_DISTANCE' 54 OUTPUT = 'OUTPUT' 55 56 def group(self): 57 return self.tr('Vector creation') 58 59 def groupId(self): 60 return 'vectorcreation' 61 62 def __init__(self): 63 super().__init__() 64 65 def initAlgorithm(self, config=None): 66 self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, 67 self.tr('Input layer'), 68 [QgsProcessing.TypeVectorLine])) 69 self.addParameter(QgsProcessingParameterNumber(self.POINTS_NUMBER, 70 self.tr('Number of points'), 71 QgsProcessingParameterNumber.Integer, 72 1, False, 1, 1000000000)) 73 self.addParameter(QgsProcessingParameterDistance(self.MIN_DISTANCE, 74 self.tr('Minimum distance between points'), 75 0, self.INPUT, False, 0, 1000000000)) 76 self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, 77 self.tr('Random points'), 78 type=QgsProcessing.TypeVectorPoint)) 79 80 def name(self): 81 return 'randompointsalongline' 82 83 def displayName(self): 84 return self.tr('Random points along line') 85 86 def processAlgorithm(self, parameters, context, feedback): 87 source = self.parameterAsSource(parameters, self.INPUT, context) 88 if source is None: 89 raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) 90 91 pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) 92 minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) 93 94 fields = QgsFields() 95 fields.append(QgsField('id', QVariant.Int, '', 10, 0)) 96 97 (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, 98 fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) 99 if sink is None: 100 raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) 101 102 nPoints = 0 103 nIterations = 0 104 maxIterations = pointCount * 200 105 featureCount = source.featureCount() 106 total = 100.0 / pointCount if pointCount else 1 107 108 index = QgsSpatialIndex() 109 points = {} 110 111 da = QgsDistanceArea() 112 da.setSourceCrs(source.sourceCrs(), context.transformContext()) 113 da.setEllipsoid(context.ellipsoid()) 114 115 request = QgsFeatureRequest() 116 117 random.seed() 118 119 ids = source.allFeatureIds() 120 121 while nIterations < maxIterations and nPoints < pointCount: 122 if feedback.isCanceled(): 123 break 124 125 # pick random feature 126 fid = random.choice(ids) 127 f = next(source.getFeatures(request.setFilterFid(fid).setSubsetOfAttributes([]))) 128 fGeom = f.geometry() 129 130 if fGeom.isMultipart(): 131 lines = fGeom.asMultiPolyline() 132 # pick random line 133 lineId = random.randint(0, len(lines) - 1) 134 vertices = lines[lineId] 135 else: 136 vertices = fGeom.asPolyline() 137 138 # pick random segment 139 if len(vertices) == 2: 140 vid = 0 141 else: 142 vid = random.randint(0, len(vertices) - 2) 143 startPoint = vertices[vid] 144 endPoint = vertices[vid + 1] 145 length = da.measureLine(startPoint, endPoint) 146 dist = length * random.random() 147 148 d = dist / (length - dist) 149 rx = (startPoint.x() + d * endPoint.x()) / (1 + d) 150 ry = (startPoint.y() + d * endPoint.y()) / (1 + d) 151 152 # generate random point 153 p = QgsPointXY(rx, ry) 154 geom = QgsGeometry.fromPointXY(p) 155 if vector.checkMinDistance(p, index, minDistance, points): 156 f = QgsFeature(nPoints) 157 f.initAttributes(1) 158 f.setFields(fields) 159 f.setAttribute('id', nPoints) 160 f.setGeometry(geom) 161 sink.addFeature(f, QgsFeatureSink.FastInsert) 162 index.addFeature(f) 163 points[nPoints] = p 164 nPoints += 1 165 feedback.setProgress(int(nPoints * total)) 166 nIterations += 1 167 168 if nPoints < pointCount: 169 feedback.pushInfo(self.tr('Could not generate requested number of random points. ' 170 'Maximum number of attempts exceeded.')) 171 172 return {self.OUTPUT: dest_id} 173