Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Optimise calculation of envelopes for MinimumBoundingGeometry alg
It's more efficient to calculate these on the fly, rather then
collecting all geometry points and then calculating.
  • Loading branch information
nyalldawson committed Sep 3, 2017
1 parent 83affdc commit b6e3542
Showing 1 changed file with 64 additions and 23 deletions.
87 changes: 64 additions & 23 deletions python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py
Expand Up @@ -38,6 +38,7 @@
QgsWkbTypes,
QgsFeatureRequest,
QgsFields,
QgsRectangle,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
Expand All @@ -53,7 +54,6 @@


class MinimumBoundingGeometry(QgisAlgorithm):

INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
TYPE = 'TYPE'
Expand All @@ -76,11 +76,13 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterField(self.FIELD,
self.tr('Field (optional, set if features should be grouped by class)'),
self.tr(
'Field (optional, set if features should be grouped by class)'),
parentLayerParameterName=self.INPUT, optional=True))
self.addParameter(QgsProcessingParameterEnum(self.TYPE,
self.tr('Geometry type'), options=self.type_names))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Bounding geometry'), QgsProcessing.TypeVectorPolygon))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Bounding geometry'),
QgsProcessing.TypeVectorPolygon))

def name(self):
return 'minimumboundinggeometry'
Expand All @@ -89,7 +91,9 @@ def displayName(self):
return self.tr('Minimum bounding geometry')

def tags(self):
return self.tr('bounding,box,bounds,envelope,minimum,oriented,rectangle,enclosing,circle,convex,hull,generalization').split(',')
return self.tr(
'bounding,box,bounds,envelope,minimum,oriented,rectangle,enclosing,circle,convex,hull,generalization').split(
',')

def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
Expand All @@ -108,13 +112,13 @@ def processAlgorithm(self, parameters, context, feedback):
if field_index >= 0:
fields.append(source.fields()[field_index])
if type == 0:
#envelope
# envelope
fields.append(QgsField('width', QVariant.Double, '', 20, 6))
fields.append(QgsField('height', QVariant.Double, '', 20, 6))
fields.append(QgsField('area', QVariant.Double, '', 20, 6))
fields.append(QgsField('perimeter', QVariant.Double, '', 20, 6))
elif type == 1:
#oriented rect
# oriented rect
fields.append(QgsField('width', QVariant.Double, '', 20, 6))
fields.append(QgsField('height', QVariant.Double, '', 20, 6))
fields.append(QgsField('angle', QVariant.Double, '', 20, 6))
Expand All @@ -134,6 +138,7 @@ def processAlgorithm(self, parameters, context, feedback):

if field_index >= 0:
geometry_dict = {}
bounds_dict = {}
total = 50.0 / source.featureCount() if source.featureCount() else 1
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_index]))
for current, f in enumerate(features):
Expand All @@ -143,41 +148,77 @@ def processAlgorithm(self, parameters, context, feedback):
if not f.hasGeometry():
continue

if not f.attributes()[field_index] in geometry_dict:
geometry_dict[f.attributes()[field_index]] = [f.geometry()]
if type == 0:
# bounding boxes - calculate on the fly for efficiency
if not f.attributes()[field_index] in bounds_dict:
bounds_dict[f.attributes()[field_index]] = f.geometry().boundingBox()
else:
bounds_dict[f.attributes()[field_index]].combineExtentWith(f.geometry().boundingBox())
else:
geometry_dict[f.attributes()[field_index]].append(f.geometry())
if not f.attributes()[field_index] in geometry_dict:
geometry_dict[f.attributes()[field_index]] = [f.geometry()]
else:
geometry_dict[f.attributes()[field_index]].append(f.geometry())

feedback.setProgress(int(current * total))

current = 0
total = 50.0 / len(geometry_dict) if geometry_dict else 1
for group, geometries in geometry_dict.items():
if feedback.isCanceled():
break

feature = self.createFeature(feedback, current, type, geometries, group)
sink.addFeature(feature, QgsFeatureSink.FastInsert)
geometry_dict[group] = None

feedback.setProgress(50 + int(current * total))
current += 1
if type == 0:
# bounding boxes
current = 0
total = 50.0 / len(bounds_dict) if bounds_dict else 1
for group, rect in bounds_dict.items():
if feedback.isCanceled():
break

# envelope
feature = QgsFeature()
feature.setGeometry(QgsGeometry.fromRect(rect))
feature.setAttributes([current, group, rect.width(), rect.height(), rect.area(), rect.perimeter()])
sink.addFeature(feature, QgsFeatureSink.FastInsert)
geometry_dict[group] = None

feedback.setProgress(50 + int(current * total))
current += 1
else:
current = 0
total = 50.0 / len(geometry_dict) if geometry_dict else 1

for group, geometries in geometry_dict.items():
if feedback.isCanceled():
break

feature = self.createFeature(feedback, current, type, geometries, group)
sink.addFeature(feature, QgsFeatureSink.FastInsert)
geometry_dict[group] = None

feedback.setProgress(50 + int(current * total))
current += 1
else:
total = 80.0 / source.featureCount() if source.featureCount() else 1
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]))
geometry_queue = []
bounds = QgsRectangle()
for current, f in enumerate(features):
if feedback.isCanceled():
break

if not f.hasGeometry():
continue

geometry_queue.append(f.geometry())
if type == 0:
# bounding boxes, calculate on the fly for efficiency
bounds.combineExtentWith(f.geometry().boundingBox())
else:
geometry_queue.append(f.geometry())
feedback.setProgress(int(current * total))

if not feedback.isCanceled():
feature = self.createFeature(feedback, 0, type, geometry_queue)
if type == 0:
feature = QgsFeature()
feature.setGeometry(QgsGeometry.fromRect(bounds))
feature.setAttributes([0, bounds.width(), bounds.height(), bounds.area(), bounds.perimeter()])
else:
feature = self.createFeature(feedback, 0, type, geometry_queue)
sink.addFeature(feature, QgsFeatureSink.FastInsert)

return {self.OUTPUT: dest_id}
Expand Down

0 comments on commit b6e3542

Please sign in to comment.