Skip to content
Permalink
Browse files

Merge pull request #5001 from nyalldawson/algs

Restore some processing functionality
  • Loading branch information
nyalldawson committed Aug 13, 2017
2 parents 35da03f + f7ae071 commit 9be7b27d8c769705156cb095750a99f62d0e9138
Showing with 792 additions and 184 deletions.
  1. +1 −1 python/plugins/processing/algs/help/qgis.yaml
  2. +94 −46 python/plugins/processing/algs/qgis/PointsDisplacement.py
  3. +26 −22 python/plugins/processing/algs/qgis/PointsFromLines.py
  4. +34 −26 python/plugins/processing/algs/qgis/PointsFromPolygons.py
  5. +6 −7 python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
  6. +7 −4 python/plugins/processing/algs/qgis/TopoColors.py
  7. +51 −60 python/plugins/processing/gui/TestTools.py
  8. +26 −0 python/plugins/processing/tests/testdata/custom/displace_points.gfs
  9. +41 −0 python/plugins/processing/tests/testdata/custom/displace_points.gml
  10. +16 −0 python/plugins/processing/tests/testdata/custom/pixel_lines.gfs
  11. +28 −0 python/plugins/processing/tests/testdata/custom/pixel_lines.gml
  12. +30 −0 python/plugins/processing/tests/testdata/custom/pixel_polygons.gfs
  13. +36 −0 python/plugins/processing/tests/testdata/custom/pixel_polygons.gml
  14. +26 −0 python/plugins/processing/tests/testdata/expected/displaced_points.gfs
  15. +35 −0 python/plugins/processing/tests/testdata/expected/displaced_points.gml
  16. +31 −0 python/plugins/processing/tests/testdata/expected/pixel_centroids_lines.gfs
  17. +78 −0 python/plugins/processing/tests/testdata/expected/pixel_centroids_lines.gml
  18. +31 −0 python/plugins/processing/tests/testdata/expected/pixel_centroids_polygon.gfs
  19. +110 −0 python/plugins/processing/tests/testdata/expected/pixel_centroids_polygon.gml
  20. +42 −0 python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
  21. +6 −5 src/core/processing/qgsprocessingfeedback.h
  22. +23 −7 src/core/processing/qgsprocessingparameters.cpp
  23. +5 −0 src/core/symbology/qgspointdistancerenderer.cpp
  24. +9 −6 tests/src/core/testqgsprocessing.cpp
@@ -360,7 +360,7 @@ qgis:pointsalonglines: >
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.

qgis:pointsdisplacement:

Offsets nearby point features by moving nearby points by a preset amount to minimize overlapping features.

qgis:pointslayerfromtable: >
This algorithm generates a points layer based on the values from an input table.
@@ -27,27 +27,26 @@
__revision__ = '$Format:%H$'

import math
from qgis.core import (QgsApplication,
QgsFeatureRequest,
QgsFeature,
QgsFeatureSink,
from qgis.core import (QgsFeatureSink,
QgsGeometry,
QgsPointXY,
QgsProcessingUtils)
from processing.tools import dataobjects
QgsSpatialIndex,
QgsRectangle,
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterNumber,
QgsProcessingParameterBoolean,
QgsProcessingParameterFeatureSink)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterBoolean
from processing.core.outputs import OutputVector


class PointsDisplacement(QgisAlgorithm):

INPUT_LAYER = 'INPUT_LAYER'
INPUT = 'INPUT'
DISTANCE = 'DISTANCE'
PROXIMITY = 'PROXIMITY'
HORIZONTAL = 'HORIZONTAL'
OUTPUT_LAYER = 'OUTPUT_LAYER'
OUTPUT = 'OUTPUT'

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

def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(ParameterNumber(self.DISTANCE,
self.tr('Displacement distance'),
0.00001, 999999999.999990, 0.00015))
self.addParameter(ParameterBoolean(self.HORIZONTAL,
self.tr('Horizontal distribution for two point case')))
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Displaced'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'), [QgsProcessing.TypeVectorPoint]))
self.addParameter(QgsProcessingParameterNumber(self.PROXIMITY,
self.tr('Minimum distance to other points'), type=QgsProcessingParameterNumber.Double,
minValue=0.00001, defaultValue=0.00015))
self.addParameter(QgsProcessingParameterNumber(self.DISTANCE,
self.tr('Displacement distance'), type=QgsProcessingParameterNumber.Double,
minValue=0.00001, defaultValue=0.00015))
self.addParameter(QgsProcessingParameterBoolean(self.HORIZONTAL,
self.tr('Horizontal distribution for two point case')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Displaced'), QgsProcessing.TypeVectorPoint))

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

def processAlgorithm(self, parameters, context, feedback):
radius = self.getParameterValue(self.DISTANCE)
horizontal = self.getParameterValue(self.HORIZONTAL)
output = self.getOutputFromName(self.OUTPUT_LAYER)
source = self.parameterAsSource(parameters, self.INPUT, context)
proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context)
radius = self.parameterAsDouble(parameters, self.DISTANCE, context)
horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context)

layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
source.fields(), source.wkbType(), source.sourceCrs())

writer = output.getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context)
features = source.getFeatures()

features = QgsProcessingUtils.getFeatures(layer, context)
total = 100.0 / source.featureCount() if source.featureCount() else 0

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

duplicates = dict()
index = QgsSpatialIndex()

# NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm,
# please port the changes to QgsPointDistanceRenderer::renderFeature also!

clustered_groups = []
group_index = {}
group_locations = {}
for current, f in enumerate(features):
wkt = f.geometry().exportToWkt()
if wkt not in duplicates:
duplicates[wkt] = [f.id()]
if feedback.isCanceled():
break

if not f.hasGeometry():
continue

point = f.geometry().asPoint()

other_features_within_radius = index.intersects(searchRect(point))
if not other_features_within_radius:
index.insertFeature(f)
group = [f]
clustered_groups.append(group)
group_index[f.id()] = len(clustered_groups) - 1
group_locations[f.id()] = point
else:
duplicates[wkt].extend([f.id()])
# find group with closest location to this point (may be more than one within search tolerance)
min_dist_feature_id = other_features_within_radius[0]
min_dist = group_locations[min_dist_feature_id].distance(point)
for i in range(1, len(other_features_within_radius)):
candidate_id = other_features_within_radius[i]
new_dist = group_locations[candidate_id].distance(point)
if new_dist < min_dist:
min_dist = new_dist
min_dist_feature_id = candidate_id

group_index_pos = group_index[min_dist_feature_id]
group = clustered_groups[group_index_pos]

# calculate new centroid of group
old_center = group_locations[min_dist_feature_id]
group_locations[min_dist_feature_id] = QgsPointXY((old_center.x() * len(group) + point.x()) / (len(group) + 1.0),
(old_center.y() * len(group) + point.y()) / (len(group) + 1.0))
# add to a group
clustered_groups[group_index_pos].append(f)
group_index[f.id()] = group_index_pos

feedback.setProgress(int(current * total))

current = 0
total = 100.0 / len(duplicates) if duplicates else 1
total = 100.0 / len(clustered_groups) if clustered_groups else 1
feedback.setProgress(0)

fullPerimeter = 2 * math.pi

for (geom, fids) in list(duplicates.items()):
count = len(fids)
for group in clustered_groups:
if feedback.isCanceled():
break

count = len(group)
if count == 1:
f = next(layer.getFeatures(QgsFeatureRequest().setFilterFid(fids[0])))
writer.addFeature(f, QgsFeatureSink.FastInsert)
sink.addFeature(group[0], QgsFeatureSink.FastInsert)
else:
angleStep = fullPerimeter / count
if count == 2 and horizontal:
currentAngle = math.pi / 2
else:
currentAngle = 0

old_point = QgsGeometry.fromWkt(geom).asPoint()
old_point = group_locations[group[0].id()]

for f in group:
if feedback.isCanceled():
break

request = QgsFeatureRequest().setFilterFids(fids).setFlags(QgsFeatureRequest.NoGeometry)
for f in layer.getFeatures(request):
sinusCurrentAngle = math.sin(currentAngle)
cosinusCurrentAngle = math.cos(currentAngle)
dx = radius * sinusCurrentAngle
dy = radius * cosinusCurrentAngle

new_point = QgsPointXY(old_point.x() + dx, old_point.y() + dy)
out_feature = QgsFeature()
out_feature.setGeometry(QgsGeometry.fromPoint(new_point))
out_feature.setAttributes(f.attributes())
# we want to keep any existing m/z values
point = f.geometry().geometry().clone()
point.setX(old_point.x() + dx)
point.setY(old_point.y() + dy)
f.setGeometry(QgsGeometry(point))

writer.addFeature(out_feature, QgsFeatureSink.FastInsert)
sink.addFeature(f, QgsFeatureSink.FastInsert)
currentAngle += angleStep

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

del writer
return {self.OUTPUT: dest_id}
@@ -29,28 +29,29 @@

from osgeo import gdal
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsApplication,
QgsFeature,
from qgis.core import (QgsFeature,
QgsFeatureSink,
QgsFields,
QgsField,
QgsGeometry,
QgsPointXY,
QgsWkbTypes,
QgsProcessingUtils)
from processing.tools import raster, dataobjects
QgsProcessing,
QgsFeatureRequest,
QgsProcessingParameterRasterLayer,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)
from processing.tools import raster
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import ParameterRaster
from processing.core.parameters import ParameterVector
from processing.core.outputs import OutputVector
from processing.tools.dataobjects import exportRasterLayer


class PointsFromLines(QgisAlgorithm):

INPUT_RASTER = 'INPUT_RASTER'
RASTER_BAND = 'RASTER_BAND'
INPUT_VECTOR = 'INPUT_VECTOR'
OUTPUT_LAYER = 'OUTPUT_LAYER'
OUTPUT = 'OUTPUT'

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

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

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

def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_VECTOR), context)
source = self.parameterAsSource(parameters, self.INPUT_VECTOR, context)

rasterPath = str(self.getParameterValue(self.INPUT_RASTER))
raster_layer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context)
rasterPath = exportRasterLayer(raster_layer)

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

writer = self.getOutputFromName(self.OUTPUT_LAYER).getVectorWriter(fields, QgsWkbTypes.Point,
layer.crs(), context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.Point, raster_layer.crs())

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

features = QgsProcessingUtils.getFeatures(layer, context)
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
features = source.getFeatures(QgsFeatureRequest().setDestinationCrs(raster_layer.crs()))
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, f in enumerate(features):
if feedback.isCanceled():
break
geom = f.geometry()
if geom.isMultipart():
lines = geom.asMultiPolyline()
@@ -112,7 +116,7 @@ def processAlgorithm(self, parameters, context, feedback):
geoTransform)

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

self.buildLine(x1, y1, x2, y2, geoTransform, writer,
self.buildLine(x1, y1, x2, y2, geoTransform, sink,
outFeature)

self.pointId = 0
self.lineId += 1

feedback.setProgress(int(current * total))

del writer
return {self.OUTPUT: dest_id}

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

0 comments on commit 9be7b27

Please sign in to comment.
You can’t perform that action at this time.