Skip to content

Commit 9be7b27

Browse files
authored
Merge pull request #5001 from nyalldawson/algs
Restore some processing functionality
2 parents 35da03f + f7ae071 commit 9be7b27

24 files changed

+792
-184
lines changed

python/plugins/processing/algs/help/qgis.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ qgis:pointsalonglines: >
360360
An optional start and end offset can be specified, which controls how far from the start and end of the geometry the points should be created.
361361

362362
qgis:pointsdisplacement:
363-
363+
Offsets nearby point features by moving nearby points by a preset amount to minimize overlapping features.
364364

365365
qgis:pointslayerfromtable: >
366366
This algorithm generates a points layer based on the values from an input table.

python/plugins/processing/algs/qgis/PointsDisplacement.py

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,26 @@
2727
__revision__ = '$Format:%H$'
2828

2929
import math
30-
from qgis.core import (QgsApplication,
31-
QgsFeatureRequest,
32-
QgsFeature,
33-
QgsFeatureSink,
30+
from qgis.core import (QgsFeatureSink,
3431
QgsGeometry,
3532
QgsPointXY,
36-
QgsProcessingUtils)
37-
from processing.tools import dataobjects
33+
QgsSpatialIndex,
34+
QgsRectangle,
35+
QgsProcessing,
36+
QgsProcessingParameterFeatureSource,
37+
QgsProcessingParameterNumber,
38+
QgsProcessingParameterBoolean,
39+
QgsProcessingParameterFeatureSink)
3840
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
39-
from processing.core.parameters import ParameterVector
40-
from processing.core.parameters import ParameterNumber
41-
from processing.core.parameters import ParameterBoolean
42-
from processing.core.outputs import OutputVector
4341

4442

4543
class PointsDisplacement(QgisAlgorithm):
4644

47-
INPUT_LAYER = 'INPUT_LAYER'
45+
INPUT = 'INPUT'
4846
DISTANCE = 'DISTANCE'
47+
PROXIMITY = 'PROXIMITY'
4948
HORIZONTAL = 'HORIZONTAL'
50-
OUTPUT_LAYER = 'OUTPUT_LAYER'
49+
OUTPUT = 'OUTPUT'
5150

5251
def group(self):
5352
return self.tr('Vector geometry tools')
@@ -56,14 +55,17 @@ def __init__(self):
5655
super().__init__()
5756

5857
def initAlgorithm(self, config=None):
59-
self.addParameter(ParameterVector(self.INPUT_LAYER,
60-
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POINT]))
61-
self.addParameter(ParameterNumber(self.DISTANCE,
62-
self.tr('Displacement distance'),
63-
0.00001, 999999999.999990, 0.00015))
64-
self.addParameter(ParameterBoolean(self.HORIZONTAL,
65-
self.tr('Horizontal distribution for two point case')))
66-
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Displaced'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
58+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
59+
self.tr('Input layer'), [QgsProcessing.TypeVectorPoint]))
60+
self.addParameter(QgsProcessingParameterNumber(self.PROXIMITY,
61+
self.tr('Minimum distance to other points'), type=QgsProcessingParameterNumber.Double,
62+
minValue=0.00001, defaultValue=0.00015))
63+
self.addParameter(QgsProcessingParameterNumber(self.DISTANCE,
64+
self.tr('Displacement distance'), type=QgsProcessingParameterNumber.Double,
65+
minValue=0.00001, defaultValue=0.00015))
66+
self.addParameter(QgsProcessingParameterBoolean(self.HORIZONTAL,
67+
self.tr('Horizontal distribution for two point case')))
68+
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Displaced'), QgsProcessing.TypeVectorPoint))
6769

6870
def name(self):
6971
return 'pointsdisplacement'
@@ -72,64 +74,110 @@ def displayName(self):
7274
return self.tr('Points displacement')
7375

7476
def processAlgorithm(self, parameters, context, feedback):
75-
radius = self.getParameterValue(self.DISTANCE)
76-
horizontal = self.getParameterValue(self.HORIZONTAL)
77-
output = self.getOutputFromName(self.OUTPUT_LAYER)
77+
source = self.parameterAsSource(parameters, self.INPUT, context)
78+
proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context)
79+
radius = self.parameterAsDouble(parameters, self.DISTANCE, context)
80+
horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context)
7881

79-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
82+
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
83+
source.fields(), source.wkbType(), source.sourceCrs())
8084

81-
writer = output.getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context)
85+
features = source.getFeatures()
8286

83-
features = QgsProcessingUtils.getFeatures(layer, context)
87+
total = 100.0 / source.featureCount() if source.featureCount() else 0
8488

85-
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
89+
def searchRect(p):
90+
return QgsRectangle(p.x() - proximity, p.y() - proximity, p.x() + proximity, p.y() + proximity)
8691

87-
duplicates = dict()
92+
index = QgsSpatialIndex()
93+
94+
# NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm,
95+
# please port the changes to QgsPointDistanceRenderer::renderFeature also!
96+
97+
clustered_groups = []
98+
group_index = {}
99+
group_locations = {}
88100
for current, f in enumerate(features):
89-
wkt = f.geometry().exportToWkt()
90-
if wkt not in duplicates:
91-
duplicates[wkt] = [f.id()]
101+
if feedback.isCanceled():
102+
break
103+
104+
if not f.hasGeometry():
105+
continue
106+
107+
point = f.geometry().asPoint()
108+
109+
other_features_within_radius = index.intersects(searchRect(point))
110+
if not other_features_within_radius:
111+
index.insertFeature(f)
112+
group = [f]
113+
clustered_groups.append(group)
114+
group_index[f.id()] = len(clustered_groups) - 1
115+
group_locations[f.id()] = point
92116
else:
93-
duplicates[wkt].extend([f.id()])
117+
# find group with closest location to this point (may be more than one within search tolerance)
118+
min_dist_feature_id = other_features_within_radius[0]
119+
min_dist = group_locations[min_dist_feature_id].distance(point)
120+
for i in range(1, len(other_features_within_radius)):
121+
candidate_id = other_features_within_radius[i]
122+
new_dist = group_locations[candidate_id].distance(point)
123+
if new_dist < min_dist:
124+
min_dist = new_dist
125+
min_dist_feature_id = candidate_id
126+
127+
group_index_pos = group_index[min_dist_feature_id]
128+
group = clustered_groups[group_index_pos]
129+
130+
# calculate new centroid of group
131+
old_center = group_locations[min_dist_feature_id]
132+
group_locations[min_dist_feature_id] = QgsPointXY((old_center.x() * len(group) + point.x()) / (len(group) + 1.0),
133+
(old_center.y() * len(group) + point.y()) / (len(group) + 1.0))
134+
# add to a group
135+
clustered_groups[group_index_pos].append(f)
136+
group_index[f.id()] = group_index_pos
94137

95138
feedback.setProgress(int(current * total))
96139

97140
current = 0
98-
total = 100.0 / len(duplicates) if duplicates else 1
141+
total = 100.0 / len(clustered_groups) if clustered_groups else 1
99142
feedback.setProgress(0)
100143

101144
fullPerimeter = 2 * math.pi
102145

103-
for (geom, fids) in list(duplicates.items()):
104-
count = len(fids)
146+
for group in clustered_groups:
147+
if feedback.isCanceled():
148+
break
149+
150+
count = len(group)
105151
if count == 1:
106-
f = next(layer.getFeatures(QgsFeatureRequest().setFilterFid(fids[0])))
107-
writer.addFeature(f, QgsFeatureSink.FastInsert)
152+
sink.addFeature(group[0], QgsFeatureSink.FastInsert)
108153
else:
109154
angleStep = fullPerimeter / count
110155
if count == 2 and horizontal:
111156
currentAngle = math.pi / 2
112157
else:
113158
currentAngle = 0
114159

115-
old_point = QgsGeometry.fromWkt(geom).asPoint()
160+
old_point = group_locations[group[0].id()]
161+
162+
for f in group:
163+
if feedback.isCanceled():
164+
break
116165

117-
request = QgsFeatureRequest().setFilterFids(fids).setFlags(QgsFeatureRequest.NoGeometry)
118-
for f in layer.getFeatures(request):
119166
sinusCurrentAngle = math.sin(currentAngle)
120167
cosinusCurrentAngle = math.cos(currentAngle)
121168
dx = radius * sinusCurrentAngle
122169
dy = radius * cosinusCurrentAngle
123170

124-
new_point = QgsPointXY(old_point.x() + dx, old_point.y() + dy)
125-
out_feature = QgsFeature()
126-
out_feature.setGeometry(QgsGeometry.fromPoint(new_point))
127-
out_feature.setAttributes(f.attributes())
171+
# we want to keep any existing m/z values
172+
point = f.geometry().geometry().clone()
173+
point.setX(old_point.x() + dx)
174+
point.setY(old_point.y() + dy)
175+
f.setGeometry(QgsGeometry(point))
128176

129-
writer.addFeature(out_feature, QgsFeatureSink.FastInsert)
177+
sink.addFeature(f, QgsFeatureSink.FastInsert)
130178
currentAngle += angleStep
131179

132180
current += 1
133181
feedback.setProgress(int(current * total))
134182

135-
del writer
183+
return {self.OUTPUT: dest_id}

python/plugins/processing/algs/qgis/PointsFromLines.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,29 @@
2929

3030
from osgeo import gdal
3131
from qgis.PyQt.QtCore import QVariant
32-
from qgis.core import (QgsApplication,
33-
QgsFeature,
32+
from qgis.core import (QgsFeature,
3433
QgsFeatureSink,
3534
QgsFields,
3635
QgsField,
3736
QgsGeometry,
3837
QgsPointXY,
3938
QgsWkbTypes,
40-
QgsProcessingUtils)
41-
from processing.tools import raster, dataobjects
39+
QgsProcessing,
40+
QgsFeatureRequest,
41+
QgsProcessingParameterRasterLayer,
42+
QgsProcessingParameterFeatureSource,
43+
QgsProcessingParameterFeatureSink)
44+
from processing.tools import raster
4245
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
43-
from processing.core.parameters import ParameterRaster
44-
from processing.core.parameters import ParameterVector
45-
from processing.core.outputs import OutputVector
46+
from processing.tools.dataobjects import exportRasterLayer
4647

4748

4849
class PointsFromLines(QgisAlgorithm):
4950

5051
INPUT_RASTER = 'INPUT_RASTER'
5152
RASTER_BAND = 'RASTER_BAND'
5253
INPUT_VECTOR = 'INPUT_VECTOR'
53-
OUTPUT_LAYER = 'OUTPUT_LAYER'
54+
OUTPUT = 'OUTPUT'
5455

5556
def group(self):
5657
return self.tr('Vector analysis tools')
@@ -59,11 +60,11 @@ def __init__(self):
5960
super().__init__()
6061

6162
def initAlgorithm(self, config=None):
62-
self.addParameter(ParameterRaster(self.INPUT_RASTER,
63-
self.tr('Raster layer')))
64-
self.addParameter(ParameterVector(self.INPUT_VECTOR,
65-
self.tr('Vector layer'), [dataobjects.TYPE_VECTOR_LINE]))
66-
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Points along line'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
63+
self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_RASTER,
64+
self.tr('Raster layer')))
65+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_VECTOR,
66+
self.tr('Vector layer'), [QgsProcessing.TypeVectorLine]))
67+
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Points from polygons'), QgsProcessing.TypeVectorPoint))
6768

6869
def name(self):
6970
return 'generatepointspixelcentroidsalongline'
@@ -72,9 +73,10 @@ def displayName(self):
7273
return self.tr('Generate points (pixel centroids) along line')
7374

7475
def processAlgorithm(self, parameters, context, feedback):
75-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_VECTOR), context)
76+
source = self.parameterAsSource(parameters, self.INPUT_VECTOR, context)
7677

77-
rasterPath = str(self.getParameterValue(self.INPUT_RASTER))
78+
raster_layer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context)
79+
rasterPath = exportRasterLayer(raster_layer)
7880

7981
rasterDS = gdal.Open(rasterPath, gdal.GA_ReadOnly)
8082
geoTransform = rasterDS.GetGeoTransform()
@@ -85,8 +87,8 @@ def processAlgorithm(self, parameters, context, feedback):
8587
fields.append(QgsField('line_id', QVariant.Int, '', 10, 0))
8688
fields.append(QgsField('point_id', QVariant.Int, '', 10, 0))
8789

88-
writer = self.getOutputFromName(self.OUTPUT_LAYER).getVectorWriter(fields, QgsWkbTypes.Point,
89-
layer.crs(), context)
90+
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
91+
fields, QgsWkbTypes.Point, raster_layer.crs())
9092

9193
outFeature = QgsFeature()
9294
outFeature.setFields(fields)
@@ -95,9 +97,11 @@ def processAlgorithm(self, parameters, context, feedback):
9597
self.lineId = 0
9698
self.pointId = 0
9799

98-
features = QgsProcessingUtils.getFeatures(layer, context)
99-
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
100+
features = source.getFeatures(QgsFeatureRequest().setDestinationCrs(raster_layer.crs()))
101+
total = 100.0 / source.featureCount() if source.featureCount() else 0
100102
for current, f in enumerate(features):
103+
if feedback.isCanceled():
104+
break
101105
geom = f.geometry()
102106
if geom.isMultipart():
103107
lines = geom.asMultiPolyline()
@@ -112,7 +116,7 @@ def processAlgorithm(self, parameters, context, feedback):
112116
geoTransform)
113117

114118
self.buildLine(x1, y1, x2, y2, geoTransform,
115-
writer, outFeature)
119+
sink, outFeature)
116120
else:
117121
points = geom.asPolyline()
118122
for i in range(len(points) - 1):
@@ -122,15 +126,15 @@ def processAlgorithm(self, parameters, context, feedback):
122126
(x1, y1) = raster.mapToPixel(p1.x(), p1.y(), geoTransform)
123127
(x2, y2) = raster.mapToPixel(p2.x(), p2.y(), geoTransform)
124128

125-
self.buildLine(x1, y1, x2, y2, geoTransform, writer,
129+
self.buildLine(x1, y1, x2, y2, geoTransform, sink,
126130
outFeature)
127131

128132
self.pointId = 0
129133
self.lineId += 1
130134

131135
feedback.setProgress(int(current * total))
132136

133-
del writer
137+
return {self.OUTPUT: dest_id}
134138

135139
def buildLine(self, startX, startY, endX, endY, geoTransform, writer, feature):
136140
if startX == endX:

0 commit comments

Comments
 (0)