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