""" *************************************************************************** RandomPointsAlongLines.py --------------------- Date : April 2014 Copyright : (C) 2014 by Alexander Bruy Email : alexander dot bruy at gmail dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** """ __author__ = "Alexander Bruy" __date__ = "April 2014" __copyright__ = "(C) 2014, Alexander Bruy" import random from qgis.PyQt.QtCore import QMetaType from qgis.core import ( Qgis, QgsField, QgsFeatureSink, QgsFeature, QgsFields, QgsGeometry, QgsPointXY, QgsWkbTypes, QgsSpatialIndex, QgsFeatureRequest, QgsDistanceArea, QgsProject, QgsProcessing, QgsProcessingException, QgsProcessingParameterDistance, QgsProcessingParameterNumber, QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink, QgsProcessingParameterDefinition, ) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.tools import vector class RandomPointsAlongLines(QgisAlgorithm): INPUT = "INPUT" POINTS_NUMBER = "POINTS_NUMBER" MIN_DISTANCE = "MIN_DISTANCE" OUTPUT = "OUTPUT" def group(self): return self.tr("Vector creation") def groupId(self): return "vectorcreation" def __init__(self): super().__init__() def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT, self.tr("Input layer"), [QgsProcessing.SourceType.TypeVectorLine], ) ) self.addParameter( QgsProcessingParameterNumber( self.POINTS_NUMBER, self.tr("Number of points"), QgsProcessingParameterNumber.Type.Integer, 1, False, 1, 1000000000, ) ) self.addParameter( QgsProcessingParameterDistance( self.MIN_DISTANCE, self.tr("Minimum distance between points"), 0, self.INPUT, False, 0, 1000000000, ) ) self.addParameter( QgsProcessingParameterFeatureSink( self.OUTPUT, self.tr("Random points"), type=QgsProcessing.SourceType.TypeVectorPoint, ) ) def name(self): return "randompointsalongline" def displayName(self): return self.tr("Random points along line") def documentationFlags(self): return Qgis.ProcessingAlgorithmDocumentationFlag.RegeneratesPrimaryKey def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT) ) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) fields = QgsFields() fields.append(QgsField("id", QMetaType.Type.Int, "", 10, 0)) (sink, dest_id) = self.parameterAsSink( parameters, self.OUTPUT, context, fields, QgsWkbTypes.Type.Point, source.sourceCrs(), QgsFeatureSink.SinkFlag.RegeneratePrimaryKey, ) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = {} da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs(), context.transformContext()) da.setEllipsoid(context.ellipsoid()) request = QgsFeatureRequest() random.seed() ids = source.allFeatureIds() while ids and nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break # pick random feature fid = random.choice(ids) try: f = next( source.getFeatures( request.setFilterFid(fid).setSubsetOfAttributes([]) ) ) except StopIteration: ids.remove(fid) continue fGeom = f.geometry() if fGeom.isEmpty(): ids.remove(fid) continue if fGeom.isMultipart(): lines = fGeom.asMultiPolyline() # pick random line lineId = random.randint(0, len(lines) - 1) vertices = lines[lineId] else: vertices = fGeom.asPolyline() # pick random segment nVertices = len(vertices) if nVertices < 2: nIterations += 1 continue if nVertices == 2: vid = 0 else: vid = random.randint(0, nVertices - 2) startPoint = vertices[vid] endPoint = vertices[vid + 1] length = da.measureLine(startPoint, endPoint) if length == 0: nIterations += 1 continue dist = length * random.random() d = dist / (length - dist) rx = (startPoint.x() + d * endPoint.x()) / (1 + d) ry = (startPoint.y() + d * endPoint.y()) / (1 + d) # generate random point p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute("id", nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.Flag.FastInsert) index.addFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo( self.tr( "Could not generate requested number of random points. " "Maximum number of attempts exceeded." ) ) sink.finalize() return {self.OUTPUT: dest_id}