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.
This commit is contained in:
Nyall Dawson 2017-09-03 18:31:31 +10:00
parent 83affdc7f5
commit b6e35428e2

View File

@ -38,6 +38,7 @@ from qgis.core import (QgsField,
QgsWkbTypes, QgsWkbTypes,
QgsFeatureRequest, QgsFeatureRequest,
QgsFields, QgsFields,
QgsRectangle,
QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSource,
QgsProcessingParameterField, QgsProcessingParameterField,
QgsProcessingParameterEnum, QgsProcessingParameterEnum,
@ -53,7 +54,6 @@ pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class MinimumBoundingGeometry(QgisAlgorithm): class MinimumBoundingGeometry(QgisAlgorithm):
INPUT = 'INPUT' INPUT = 'INPUT'
OUTPUT = 'OUTPUT' OUTPUT = 'OUTPUT'
TYPE = 'TYPE' TYPE = 'TYPE'
@ -76,11 +76,13 @@ class MinimumBoundingGeometry(QgisAlgorithm):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'))) self.tr('Input layer')))
self.addParameter(QgsProcessingParameterField(self.FIELD, 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)) parentLayerParameterName=self.INPUT, optional=True))
self.addParameter(QgsProcessingParameterEnum(self.TYPE, self.addParameter(QgsProcessingParameterEnum(self.TYPE,
self.tr('Geometry type'), options=self.type_names)) 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): def name(self):
return 'minimumboundinggeometry' return 'minimumboundinggeometry'
@ -89,7 +91,9 @@ class MinimumBoundingGeometry(QgisAlgorithm):
return self.tr('Minimum bounding geometry') return self.tr('Minimum bounding geometry')
def tags(self): 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): def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context) source = self.parameterAsSource(parameters, self.INPUT, context)
@ -108,13 +112,13 @@ class MinimumBoundingGeometry(QgisAlgorithm):
if field_index >= 0: if field_index >= 0:
fields.append(source.fields()[field_index]) fields.append(source.fields()[field_index])
if type == 0: if type == 0:
#envelope # envelope
fields.append(QgsField('width', QVariant.Double, '', 20, 6)) fields.append(QgsField('width', QVariant.Double, '', 20, 6))
fields.append(QgsField('height', QVariant.Double, '', 20, 6)) fields.append(QgsField('height', QVariant.Double, '', 20, 6))
fields.append(QgsField('area', QVariant.Double, '', 20, 6)) fields.append(QgsField('area', QVariant.Double, '', 20, 6))
fields.append(QgsField('perimeter', QVariant.Double, '', 20, 6)) fields.append(QgsField('perimeter', QVariant.Double, '', 20, 6))
elif type == 1: elif type == 1:
#oriented rect # oriented rect
fields.append(QgsField('width', QVariant.Double, '', 20, 6)) fields.append(QgsField('width', QVariant.Double, '', 20, 6))
fields.append(QgsField('height', QVariant.Double, '', 20, 6)) fields.append(QgsField('height', QVariant.Double, '', 20, 6))
fields.append(QgsField('angle', QVariant.Double, '', 20, 6)) fields.append(QgsField('angle', QVariant.Double, '', 20, 6))
@ -134,6 +138,7 @@ class MinimumBoundingGeometry(QgisAlgorithm):
if field_index >= 0: if field_index >= 0:
geometry_dict = {} geometry_dict = {}
bounds_dict = {}
total = 50.0 / source.featureCount() if source.featureCount() else 1 total = 50.0 / source.featureCount() if source.featureCount() else 1
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_index])) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_index]))
for current, f in enumerate(features): for current, f in enumerate(features):
@ -143,29 +148,56 @@ class MinimumBoundingGeometry(QgisAlgorithm):
if not f.hasGeometry(): if not f.hasGeometry():
continue continue
if not f.attributes()[field_index] in geometry_dict: if type == 0:
geometry_dict[f.attributes()[field_index]] = [f.geometry()] # 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: 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)) feedback.setProgress(int(current * total))
current = 0 if type == 0:
total = 50.0 / len(geometry_dict) if geometry_dict else 1 # bounding boxes
for group, geometries in geometry_dict.items(): current = 0
if feedback.isCanceled(): total = 50.0 / len(bounds_dict) if bounds_dict else 1
break for group, rect in bounds_dict.items():
if feedback.isCanceled():
break
feature = self.createFeature(feedback, current, type, geometries, group) # envelope
sink.addFeature(feature, QgsFeatureSink.FastInsert) feature = QgsFeature()
geometry_dict[group] = None 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)) feedback.setProgress(50 + int(current * total))
current += 1 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: else:
total = 80.0 / source.featureCount() if source.featureCount() else 1 total = 80.0 / source.featureCount() if source.featureCount() else 1
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([])) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]))
geometry_queue = [] geometry_queue = []
bounds = QgsRectangle()
for current, f in enumerate(features): for current, f in enumerate(features):
if feedback.isCanceled(): if feedback.isCanceled():
break break
@ -173,11 +205,20 @@ class MinimumBoundingGeometry(QgisAlgorithm):
if not f.hasGeometry(): if not f.hasGeometry():
continue 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)) feedback.setProgress(int(current * total))
if not feedback.isCanceled(): 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) sink.addFeature(feature, QgsFeatureSink.FastInsert)
return {self.OUTPUT: dest_id} return {self.OUTPUT: dest_id}