Skip to content

Commit

Permalink
Merge pull request #5001 from nyalldawson/algs
Browse files Browse the repository at this point in the history
Restore some processing functionality
  • Loading branch information
nyalldawson committed Aug 13, 2017
2 parents 35da03f + f7ae071 commit 9be7b27
Show file tree
Hide file tree
Showing 24 changed files with 792 additions and 184 deletions.
2 changes: 1 addition & 1 deletion python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -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.
Expand Down
140 changes: 94 additions & 46 deletions python/plugins/processing/algs/qgis/PointsDisplacement.py
Expand Up @@ -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')
Expand All @@ -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'
Expand All @@ -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}
48 changes: 26 additions & 22 deletions python/plugins/processing/algs/qgis/PointsFromLines.py
Expand Up @@ -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')
Expand All @@ -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'
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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):
Expand All @@ -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:
Expand Down

0 comments on commit 9be7b27

Please sign in to comment.