From 83affdc7f531a77cdb963fa8285fdc7af9015c76 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 14:46:16 +1000 Subject: [PATCH 1/7] [FEATURE] New processing algorithm "minimum bounding geometry" This algorithm creates geometries which enclose the features from an input layer. Numerous enclosing geometry types are supported, including bounding boxes (envelopes), oriented rectangles, circles and convex hulls. Optionally, the features can be grouped by a field. If set, this causes the output layer to contain one feature per grouped value with a minimal geometry covering just the features with matching values. --- python/plugins/processing/algs/help/qgis.yaml | 7 + .../algs/qgis/MinimumBoundingGeometry.py | 237 ++++++++++++++++++ .../algs/qgis/QGISAlgorithmProvider.py | 2 + .../testdata/expected/mbg_circle_field.gfs | 37 +++ .../testdata/expected/mbg_circle_field.gml | 50 ++++ .../testdata/expected/mbg_circle_nofield.gfs | 31 +++ .../testdata/expected/mbg_circle_nofield.gml | 22 ++ .../tests/testdata/expected/mbg_env_field.gfs | 47 ++++ .../tests/testdata/expected/mbg_env_field.gml | 58 +++++ .../testdata/expected/mbg_env_nofield.dbf | Bin 0 -> 288 bytes .../testdata/expected/mbg_env_nofield.gfs | 41 +++ .../testdata/expected/mbg_env_nofield.gml | 24 ++ .../testdata/expected/mbg_env_nofield.prj | 1 + .../testdata/expected/mbg_env_nofield.qpj | 1 + .../testdata/expected/mbg_env_nofield.shp | Bin 0 -> 236 bytes .../testdata/expected/mbg_env_nofield.shx | Bin 0 -> 108 bytes .../testdata/expected/mbg_hull_field.gfs | 37 +++ .../testdata/expected/mbg_hull_field.gml | 50 ++++ .../testdata/expected/mbg_hull_nofield.gfs | 31 +++ .../testdata/expected/mbg_hull_nofield.gml | 22 ++ .../testdata/expected/mbg_rect_field.gfs | 52 ++++ .../testdata/expected/mbg_rect_field.gml | 62 +++++ .../testdata/expected/mbg_rect_nofield.gfs | 46 ++++ .../testdata/expected/mbg_rect_nofield.gml | 25 ++ .../tests/testdata/qgis_algorithm_tests.yaml | 152 +++++++++++ 25 files changed, 1035 insertions(+) create mode 100644 python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_circle_field.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_circle_field.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_field.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_field.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.dbf create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.prj create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.qpj create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.shp create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_env_nofield.shx create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_hull_field.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_hull_field.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_rect_field.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_rect_field.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gml diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index 9fa8531bd82..a488f121bd7 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -324,6 +324,13 @@ qgis:mergevectorlayers: > The layers will all be reprojected to match the coordinate reference system of the first input layer. +qgis:minimumboundinggeometry: > + This algorithm creates geometries which enclose the features from an input layer. + + Numerous enclosing geometry types are supported, including bounding boxes (envelopes), oriented rectangles, circles and convex hulls. + + Optionally, the features can be grouped by a field. If set, this causes the output layer to contain one feature per grouped value with a minimal geometry covering just the features with matching values. + qgis:multiparttosingleparts: > This algorithm takes a vector layer with multipart geometries and generates a new one in which all geometries contain a single part. Features with multipart geometries are divided in as many different features as parts the geometry contain, and the same attributes are used for each of them. diff --git a/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py b/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py new file mode 100644 index 00000000000..de4f9041b27 --- /dev/null +++ b/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + MinimumBoundingGeometry.py + -------------------------- + Date : September 2017 + Copyright : (C) 2017 by Nyall Dawson + Email : nyall dot dawson at gmail dot com +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" +from builtins import str + +__author__ = 'Nyall Dawson' +__date__ = 'September 2017' +__copyright__ = '(C) 2017, Nyall Dawson' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import os +import math + +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import QVariant + +from qgis.core import (QgsField, + QgsFeatureSink, + QgsGeometry, + QgsWkbTypes, + QgsFeatureRequest, + QgsFields, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterField, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSink, + QgsProcessing, + QgsFeature, + QgsVertexId, + QgsMultiPointV2) + +from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm + +pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] + + +class MinimumBoundingGeometry(QgisAlgorithm): + + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' + TYPE = 'TYPE' + FIELD = 'FIELD' + + def icon(self): + return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'convex_hull.png')) + + def group(self): + return self.tr('Vector geometry') + + def __init__(self): + super().__init__() + self.type_names = [self.tr('Envelope (Bounding Box)'), + self.tr('Minimum Oriented Rectangle'), + self.tr('Minimum Enclosing Circle'), + self.tr('Convex Hull')] + + 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)'), + 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)) + + def name(self): + return 'minimumboundinggeometry' + + 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(',') + + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + field_name = self.parameterAsString(parameters, self.FIELD, context) + type = self.parameterAsEnum(parameters, self.TYPE, context) + use_field = bool(field_name) + + field_index = -1 + + fields = QgsFields() + fields.append(QgsField('id', QVariant.Int, '', 20)) + + if use_field: + # keep original field type, name and parameters + field_index = source.fields().lookupField(field_name) + if field_index >= 0: + fields.append(source.fields()[field_index]) + if type == 0: + #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 + fields.append(QgsField('width', QVariant.Double, '', 20, 6)) + fields.append(QgsField('height', QVariant.Double, '', 20, 6)) + fields.append(QgsField('angle', QVariant.Double, '', 20, 6)) + fields.append(QgsField('area', QVariant.Double, '', 20, 6)) + fields.append(QgsField('perimeter', QVariant.Double, '', 20, 6)) + elif type == 2: + # circle + fields.append(QgsField('radius', QVariant.Double, '', 20, 6)) + fields.append(QgsField('area', QVariant.Double, '', 20, 6)) + elif type == 3: + # convex hull + fields.append(QgsField('area', QVariant.Double, '', 20, 6)) + fields.append(QgsField('perimeter', QVariant.Double, '', 20, 6)) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, source.sourceCrs()) + + if field_index >= 0: + geometry_dict = {} + total = 50.0 / source.featureCount() if source.featureCount() else 1 + features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_index])) + for current, f in enumerate(features): + if feedback.isCanceled(): + break + + if not f.hasGeometry(): + continue + + 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 + else: + total = 80.0 / source.featureCount() if source.featureCount() else 1 + features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([])) + geometry_queue = [] + for current, f in enumerate(features): + if feedback.isCanceled(): + break + + if not f.hasGeometry(): + continue + + geometry_queue.append(f.geometry()) + feedback.setProgress(int(current * total)) + + if not feedback.isCanceled(): + feature = self.createFeature(feedback, 0, type, geometry_queue) + sink.addFeature(feature, QgsFeatureSink.FastInsert) + + return {self.OUTPUT: dest_id} + + def createFeature(self, feedback, feature_id, type, geometries, class_field=None): + attrs = [feature_id] + if class_field is not None: + attrs.append(class_field) + + multi_point = QgsMultiPointV2() + + for g in geometries: + if feedback.isCanceled(): + break + + vid = QgsVertexId() + while True: + if feedback.isCanceled(): + break + found, point = g.geometry().nextVertex(vid) + if found: + multi_point.addGeometry(point) + else: + break + + geometry = QgsGeometry(multi_point) + output_geometry = None + if type == 0: + # envelope + rect = geometry.boundingBox() + output_geometry = QgsGeometry.fromRect(rect) + attrs.append(rect.width()) + attrs.append(rect.height()) + attrs.append(rect.area()) + attrs.append(rect.perimeter()) + elif type == 1: + # oriented rect + output_geometry, area, angle, width, height = geometry.orientedMinimumBoundingBox() + attrs.append(width) + attrs.append(height) + attrs.append(angle) + attrs.append(area) + attrs.append(2 * width + 2 * height) + elif type == 2: + # circle + output_geometry, center, radius = geometry.minimalEnclosingCircle(segments=72) + attrs.append(radius) + attrs.append(math.pi * radius * radius) + elif type == 3: + # convex hull + output_geometry = geometry.convexHull() + attrs.append(output_geometry.geometry().area()) + attrs.append(output_geometry.geometry().perimeter()) + f = QgsFeature() + f.setAttributes(attrs) + f.setGeometry(output_geometry) + return f diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 1a532f004dc..7299b188c5c 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -100,6 +100,7 @@ from .LinesToPolygons import LinesToPolygons from .MeanCoords import MeanCoords from .Merge import Merge from .MergeLines import MergeLines +from .MinimumBoundingGeometry import MinimumBoundingGeometry from .MinimalEnclosingCircle import MinimalEnclosingCircle from .NearestNeighbourAnalysis import NearestNeighbourAnalysis from .OffsetLine import OffsetLine @@ -254,6 +255,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): MeanCoords(), Merge(), MergeLines(), + MinimumBoundingGeometry(), MinimalEnclosingCircle(), NearestNeighbourAnalysis(), OffsetLine(), diff --git a/python/plugins/processing/tests/testdata/expected/mbg_circle_field.gfs b/python/plugins/processing/tests/testdata/expected/mbg_circle_field.gfs new file mode 100644 index 00000000000..6f4e6c288a7 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_circle_field.gfs @@ -0,0 +1,37 @@ + + + mbg_circle_field + mbg_circle_field + + 3 + EPSG:4326 + + 4 + -2.10952 + 9.37006 + -4.60952 + 7.10328 + + + id + id + Integer + + + name + name + String + 2 + + + radius + radius + Real + + + area + area + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_circle_field.gml b/python/plugins/processing/tests/testdata/expected/mbg_circle_field.gml new file mode 100644 index 00000000000..f941136cf1b --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_circle_field.gml @@ -0,0 +1,50 @@ + + + + + -2.109518379976041-4.609518379976041 + 9.3700626353132297.103275517095002 + + + + + + -1.0,3.0 -0.725214215078135,3.29362919389204 -0.425882602541937,3.56219188087088 -0.104283256704178,3.80364413672603 0.237136257226325,4.01614836399757 0.595777530593825,4.1980872772024 0.968911086754466,4.34807621135332 1.35369715404167,4.46497366009564 1.7472072781432,4.54788996325982 2.14644660940673,4.59619407771256 2.54837669545405,4.60951837997604 2.94993860563831,4.58776146406461 3.34807621135331,4.53108891324554 3.7397594450175,4.43993203985037 4.12200736071789,4.31498460272768 4.49191082100838,4.1571975273193 4.84665463720237,3.96777166854352 5.18353899465843,3.74814867156408 5.5,3.5 5.79362919389204,3.22521421507814 6.06219188087088,2.92588260254194 6.30364413672603,2.60428325670418 6.51614836399756,2.26286374277367 6.6980872772024,1.90422246940618 6.84807621135332,1.53108891324554 6.96497366009564,1.14630284595834 7.04788996325982,0.752792721856805 7.09619407771256,0.353553390593274 7.10951837997604,-0.048376695454046 7.08776146406461,-0.449938605638313 7.03108891324554,-0.848076211353317 6.93993203985037,-1.2397594450175 6.81498460272769,-1.62200736071788 6.6571975273193,-1.99191082100838 6.46777166854352,-2.34665463720237 6.24814867156408,-2.68353899465843 6.0,-3.0 5.72521421507814,-3.29362919389204 5.42588260254194,-3.56219188087088 5.10428325670418,-3.80364413672603 4.76286374277367,-4.01614836399757 4.40422246940618,-4.1980872772024 4.03108891324554,-4.34807621135332 3.64630284595833,-4.46497366009564 3.2527927218568,-4.54788996325982 2.85355339059327,-4.59619407771256 2.45162330454595,-4.60951837997604 2.05006139436169,-4.58776146406461 1.65192378864669,-4.53108891324554 1.2602405549825,-4.43993203985037 0.877992639282118,-4.31498460272769 0.508089178991617,-4.1571975273193 0.153345362797632,-3.96777166854352 -0.183538994658433,-3.74814867156408 -0.5,-3.5 -0.793629193892039,-3.22521421507814 -1.06219188087088,-2.92588260254194 -1.30364413672603,-2.60428325670418 -1.51614836399756,-2.26286374277368 -1.6980872772024,-1.90422246940618 -1.84807621135332,-1.53108891324553 -1.96497366009564,-1.14630284595833 -2.04788996325982,-0.752792721856805 -2.09619407771256,-0.353553390593274 -2.10951837997604,0.048376695454046 -2.08776146406461,0.449938605638312 -2.03108891324554,0.848076211353314 -1.93993203985037,1.2397594450175 -1.81498460272769,1.62200736071788 -1.6571975273193,1.99191082100838 -1.46777166854352,2.34665463720237 -1.24814867156408,2.68353899465843 -1.0,3.0 + 0 + aa + 4.609772 + 66.758844 + + + + + 7.24145873320538,-1.05451055662188 7.23765343129712,-1.14166629936954 7.22626648621758,-1.22815873428881 7.20738455949444,-1.3133296017244 7.18115135399128,-1.39653069994755 7.14776652024203,-1.47712881836258 7.10748413698981,-1.55451055662188 7.06061077749437,-1.62808699297293 7.00750317632435,-1.69729816630842 6.94856551439192,-1.76161733780843 6.88424634289191,-1.82055499974086 6.81503516955642,-1.87366260091087 6.74145873320538,-1.92053596040632 6.66407699494607,-1.96081834365853 6.58347887653104,-1.99420317740779 6.5002777783079,-2.02043638291095 6.41510691087231,-2.03931830963409 6.32861447595303,-2.05070525471363 6.24145873320538,-2.05451055662188 6.15430299045772,-2.05070525471363 6.06781055553845,-2.03931830963409 5.98263968810285,-2.02043638291095 5.89943858987971,-1.99420317740779 5.81884047146468,-1.96081834365853 5.74145873320538,-1.92053596040632 5.66788229685433,-1.87366260091087 5.59867112351884,-1.82055499974086 5.53435195201883,-1.76161733780843 5.4754142900864,-1.69729816630842 5.42230668891638,-1.62808699297293 5.37543332942094,-1.55451055662188 5.33515094616873,-1.47712881836258 5.30176611241947,-1.39653069994755 5.27553290691631,-1.3133296017244 5.25665098019317,-1.22815873428881 5.24526403511363,-1.14166629936954 5.24145873320538,-1.05451055662188 5.24526403511363,-0.967354813874224 5.25665098019317,-0.880862378954952 5.27553290691631,-0.795691511519361 5.30176611241947,-0.712490413296213 5.33515094616873,-0.631892294881182 5.37543332942094,-0.554510556621882 5.42230668891638,-0.480934120270836 5.4754142900864,-0.411722946935343 5.53435195201883,-0.347403775435334 5.59867112351884,-0.288466113502904 5.66788229685433,-0.23535851233289 5.74145873320538,-0.188485152837443 5.81884047146468,-0.148202769585232 5.89943858987971,-0.114817935835974 5.98263968810285,-0.088584730332814 6.06781055553845,-0.069702803609674 6.15430299045772,-0.058315858530136 6.24145873320538,-0.054510556621882 6.32861447595303,-0.058315858530136 6.41510691087231,-0.069702803609674 6.5002777783079,-0.088584730332814 6.58347887653104,-0.114817935835973 6.66407699494607,-0.148202769585232 6.74145873320538,-0.188485152837443 6.81503516955642,-0.23535851233289 6.88424634289191,-0.288466113502904 6.94856551439192,-0.347403775435335 7.00750317632435,-0.411722946935343 7.06061077749437,-0.480934120270836 7.10748413698981,-0.554510556621882 7.14776652024203,-0.631892294881182 7.18115135399128,-0.712490413296213 7.20738455949444,-0.795691511519361 7.22626648621758,-0.880862378954952 7.23765343129712,-0.967354813874224 7.24145873320538,-1.05451055662188 + 1 + dd + 1.000000 + 3.141593 + + + + + 5.17255278310941,4.82264875239923 5.11521006129984,4.6866357437648 5.04623123106812,4.55613805320215 4.96614126296287,4.43214884693314 4.87554969000102,4.31561175768419 4.77514596875349,4.20741370307151 4.66569423216439,4.10837813562248 4.54802747403795,4.01925877580469 4.42304120945263,3.9407338757581 4.29168665935069,3.87340105738669 4.15496351117246,3.81777276409493 4.01391231063126,3.77427236078406 3.86960654253227,3.74323091178954 3.72314446090491,3.72488466128155 3.5756407306266,3.71937323530423 3.42821794414994,3.72673857913715 3.28199807789633,3.74692463806644 3.13809395333793,3.77977778399495 2.99760076775432,3.82504798464491 2.86158775911988,3.88239070645448 2.73109006855724,3.9513695366862 2.60710086228822,4.03145950479144 2.49056377303928,4.1220510777533 2.3823657184266,4.22245479900083 2.28333015097757,4.33190653558992 2.19421079115978,4.44957329371637 2.11568589111318,4.57455955830169 2.04835307274177,4.70591410840363 1.99272477945002,4.84263725658186 1.94922437613915,4.98368845712305 1.91818292714463,5.12799422522205 1.89983667663664,5.2744563068494 1.89432525065932,5.42196003712771 1.90169059449224,5.56938282360437 1.92187665342152,5.71560268985798 1.95472979935004,5.85950681441638 2,6 2.05734272180957,6.13601300863443 2.12632155204128,6.26651069919708 2.20641152014653,6.3904999054661 2.29700309310839,6.50703699471504 2.39740681435592,6.61523504932772 2.50685855094501,6.71427061677675 2.62452530907146,6.80338997659454 2.74951157365678,6.88191487664113 2.88086612375872,6.94924769501255 3.01758927193695,7.0048759883043 3.15864047247814,7.04837639161517 3.30294624057714,7.07941784060969 3.44940832220449,7.09776409111768 3.5969120524828,7.103275517095 3.74433483895946,7.09591017326208 3.89055470521307,7.0757241143328 4.03445882977147,7.04287096840428 4.17495201535509,6.99760076775432 4.31096502398952,6.94025804594475 4.44146271455217,6.87127921571304 4.56545192082118,6.79118924760779 4.68198901007013,6.70059767464593 4.79018706468281,6.6001939533984 4.88922263213184,6.49074221680931 4.97834199194963,6.37307545868286 5.05686689199622,6.24808919409754 5.12419971036763,6.1167346439956 5.17982800365939,5.98001149581737 5.22332840697026,5.83896029527618 5.25436985596478,5.69465452717718 5.27271610647277,5.54819244554983 5.27822753245009,5.40068871527152 5.27086218861717,5.25326592879486 5.25067612968788,5.10704606254125 5.21782298375937,4.96314193798285 5.17255278310941,4.82264875239923 + 2 + bb + 1.691985 + 8.993788 + + + + + 9.16295585412668,3.73877159309021 9.20463107454638,3.69329107076225 9.24218381946625,3.64435138076285 9.27532828982248,3.59232498368348 9.30381223618247,3.53760783182033 9.32741887851536,3.48061635573819 9.3459685560189,3.42178429498243 9.3593200944467,3.36155939705918 9.36737188052944,3.30040000980643 9.37006263531323,3.23877159309021 9.36737188052944,3.17714317637399 9.3593200944467,3.11598378912124 9.3459685560189,3.05575889119799 9.32741887851536,2.99692683044224 9.30381223618247,2.93993535436009 9.27532828982248,2.88521820249694 9.24218381946625,2.83319180541757 9.20463107454638,2.78425211541817 9.16295585412668,2.73877159309021 9.11747533179873,2.69709637267051 9.06853564179932,2.65954362775064 9.01650924471996,2.62639915739442 8.9617920928568,2.59791521103442 8.90480061677466,2.57430856870154 8.8459685560189,2.55575889119799 8.78574365809565,2.54240735277019 8.7245842708429,2.53435556668745 8.66295585412668,2.53166481190366 8.60132743741046,2.53435556668745 8.54016805015771,2.54240735277019 8.47994315223446,2.55575889119799 8.42111109147871,2.57430856870154 8.36411961539656,2.59791521103442 8.30940246353341,2.62639915739442 8.25737606645404,2.65954362775064 8.20843637645464,2.69709637267051 8.16295585412668,2.73877159309021 8.12128063370698,2.78425211541817 8.08372788878711,2.83319180541757 8.05058341843089,2.88521820249694 8.02209947207089,2.93993535436009 7.99849282973801,2.99692683044224 7.97994315223446,3.05575889119799 7.96659161380666,3.11598378912124 7.95853982772392,3.17714317637399 7.95584907294014,3.23877159309021 7.95853982772392,3.30040000980643 7.96659161380666,3.36155939705918 7.97994315223446,3.42178429498243 7.99849282973801,3.48061635573819 8.02209947207089,3.53760783182033 8.05058341843089,3.59232498368348 8.08372788878711,3.64435138076285 8.12128063370698,3.69329107076225 8.16295585412668,3.73877159309021 8.20843637645464,3.78044681350991 8.25737606645404,3.81799955842978 8.30940246353341,3.85114402878601 8.36411961539656,3.879627975146 8.42111109147871,3.90323461747889 8.47994315223446,3.92178429498243 8.54016805015771,3.93513583341023 8.60132743741046,3.94318761949297 8.66295585412668,3.94587837427676 8.7245842708429,3.94318761949297 8.78574365809565,3.93513583341023 8.8459685560189,3.92178429498243 8.90480061677466,3.90323461747889 8.9617920928568,3.879627975146 9.01650924471996,3.85114402878601 9.06853564179932,3.81799955842978 9.11747533179873,3.78044681350991 9.16295585412668,3.73877159309021 + 3 + cc + 0.707107 + 1.570796 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gfs b/python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gfs new file mode 100644 index 00000000000..9480f0eb975 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gfs @@ -0,0 +1,31 @@ + + + mbg_circle_nofield + mbg_circle_nofield + + 3 + EPSG:4326 + + 1 + -1.52525 + 9.68821 + -4.23734 + 6.97611 + + + id + id + Integer + + + radius + radius + Real + + + area + area + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gml b/python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gml new file mode 100644 index 00000000000..861bd973050 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_circle_nofield.gml @@ -0,0 +1,22 @@ + + + + + -1.525250794618014-4.237342925136248 + 9.6882066487446966.976114518226461 + + + + + + 9.16295585412668,3.73877159309021 9.35012487542772,3.28687538180819 9.49719631215569,2.82038591697045 9.60305086187294,2.34285346507845 9.6668829075394,1.85791233643306 9.6882066487447,1.36925322583872 9.66685979894264,0.880595124187521 9.60300482054979,0.395657014692835 9.49712768850842,-0.081870430818441 9.35003419172372,-0.548352940147011 9.16284380052371,-1.00024029972705 8.93698114681406,-1.43409337389509 8.6741651817691,-1.84661027878908 8.37639609357545,-2.23465151167826 8.04594008479214,-2.59526384447463 7.68531212518082,-2.92570279958194 7.29725681126725,-3.22345353702731 6.8847274783045,-3.48624999391199 6.45086372360845,-3.71209213051824 5.99896751232643,-3.89926115181927 5.53247804748869,-4.04633258854724 5.05494559559668,-4.15218713826449 4.57000446695129,-4.21601918393095 4.08134535635697,-4.23734292513625 3.59268725470576,-4.21599607533419 3.10774914521108,-4.15214109694135 2.6302216996998,-4.04626396489997 2.16373919037122,-3.89917046811528 1.71185183079119,-3.71198007691526 1.27799875662314,-3.48611742320561 0.865481851729151,-3.22330145816065 0.477440618839976,-2.925532369967 0.116828286043605,-2.5950763611837 -0.213610669063699,-2.23444840157238 -0.511361406509074,-1.8463930876588 -0.774157863393753,-1.43386375469605 -1.0,-1.0 -1.18716902130103,-0.54810378871798 -1.33424045802901,-0.081614323880241 -1.44009500774626,0.395918128011764 -1.50392705341272,0.880859256657155 -1.52525079461801,1.36951836725148 -1.50390394481595,1.85817646890269 -1.44004896642311,2.34311457839737 -1.33417183438174,2.82064202390865 -1.18707833759704,3.28712453323722 -0.999887946397026,3.73901189281726 -0.774025292687375,4.1728649669853 -0.511209327642419,4.58538187187929 -0.213440239448766,4.97342310476847 0.117015769334537,5.33403543756484 0.477643728945859,5.66447439267215 0.865699042859437,5.96222513011752 1.27822837582218,6.2250215870022 1.71209213051824,6.45086372360845 2.16398834180026,6.63803274490948 2.63047780663799,6.78510418163745 3.10801025853,6.8909587313547 3.59295138717539,6.95479077702116 4.08161049776972,6.97611451822646 4.57026859942092,6.9547676684244 5.05520670891561,6.89091269003156 5.53273415442689,6.78503555799019 5.99921666375546,6.63794206120549 6.45110402333549,6.45075167000547 6.88495709750354,6.22488901629582 7.29747400239753,5.96207305125087 7.6855152352867,5.66430396305721 8.04612756808307,5.33384795427391 8.37656652319038,4.97321999466259 8.67431726063576,4.58516468074901 8.93711371752043,4.17263534778626 9.16295585412668,3.73877159309021 + 0 + 5.606729 + 98.757244 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_field.gfs b/python/plugins/processing/tests/testdata/expected/mbg_env_field.gfs new file mode 100644 index 00000000000..958c6c83e5a --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_env_field.gfs @@ -0,0 +1,47 @@ + + + mbg_env_field + mbg_env_field + + 3 + EPSG:4326 + + 4 + -1.00000 + 9.16296 + -3.00000 + 6.08868 + + + id + id + Integer + + + name + name + String + 2 + + + width + width + Real + + + height + height + Real + + + area + area + Real + + + perimeter + perimeter + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_field.gml b/python/plugins/processing/tests/testdata/expected/mbg_env_field.gml new file mode 100644 index 00000000000..1d7be01a727 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_env_field.gml @@ -0,0 +1,58 @@ + + + + + -1-3 + 9.1629558541266826.088675623800385 + + + + + + -1,-3 6,-3 6,3 -1,3 -1,-3 + 0 + aa + 7.000000 + 6.000000 + 42.000000 + 26.000000 + + + + + 5.24145873320538,-1.05451055662188 7.24145873320538,-1.05451055662188 7.24145873320538,-0.054510556621882 5.24145873320538,-0.054510556621882 5.24145873320538,-1.05451055662188 + 1 + dd + 2.000000 + 1.000000 + 2.000000 + 6.000000 + + + + + 2.0,4.42360844529751 5.17255278310941,4.42360844529751 5.17255278310941,6.08867562380038 2.0,6.08867562380038 2.0,4.42360844529751 + 2 + bb + 3.172553 + 1.665067 + 5.282514 + 9.675240 + + + + + 8.16295585412668,2.73877159309021 9.16295585412668,2.73877159309021 9.16295585412668,3.73877159309021 8.16295585412668,3.73877159309021 8.16295585412668,2.73877159309021 + 3 + cc + 1.000000 + 1.000000 + 1.000000 + 4.000000 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.dbf b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.dbf new file mode 100644 index 0000000000000000000000000000000000000000..415dc9dfd79b1e7546910e34707a1788d8f58257 GIT binary patch literal 288 zcmZQB5FL150F;#uj>}2F6Ba#sDR|9VY+) literal 0 HcmV?d00001 diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gfs b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gfs new file mode 100644 index 00000000000..96a4d54c272 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gfs @@ -0,0 +1,41 @@ + + + mbg_env_nofield + mbg_env_nofield + + 3 + EPSG:4326 + + 1 + -1.00000 + 9.16296 + -3.00000 + 6.08868 + + + id + id + Integer + + + width + width + Real + + + height + height + Real + + + area + area + Real + + + perimeter + perimeter + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gml b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gml new file mode 100644 index 00000000000..dd0621e7d38 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.gml @@ -0,0 +1,24 @@ + + + + + -1-3 + 9.1629558541266826.088675623800385 + + + + + + -1,-3 9.16295585412668,-3.0 9.16295585412668,6.08867562380038 -1.0,6.08867562380038 -1,-3 + 0 + 10.162956 + 9.088676 + 92.367809 + 38.503263 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.prj b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.prj new file mode 100644 index 00000000000..a30c00a55de --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.prj @@ -0,0 +1 @@ +GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.qpj b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.qpj new file mode 100644 index 00000000000..5fbc831e743 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.shp b/python/plugins/processing/tests/testdata/expected/mbg_env_nofield.shp new file mode 100644 index 0000000000000000000000000000000000000000..24bc5d2a014677e01df1b7ea5e375370e5a79eed GIT binary patch literal 236 zcmZQzQ0HR64$59IGcd5i + + mbg_hull_field + mbg_hull_field + + 3 + EPSG:4326 + + 4 + -1.00000 + 9.16296 + -3.00000 + 6.08868 + + + id + id + Integer + + + name + name + String + 2 + + + area + area + Real + + + perimeter + perimeter + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_hull_field.gml b/python/plugins/processing/tests/testdata/expected/mbg_hull_field.gml new file mode 100644 index 00000000000..71ee4ad10e4 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_hull_field.gml @@ -0,0 +1,50 @@ + + + + + -1-3 + 9.1629558541266826.088675623800385 + + + + + + 6,-3 -1,-1 -1,3 3,3 6,1 6,-3 + 0 + aa + 32.000000 + 22.885661 + + + + + 5.24145873320538,-1.05451055662188 6.24145873320538,-0.054510556621882 7.24145873320538,-1.05451055662188 5.24145873320538,-1.05451055662188 + 1 + dd + 1.000000 + 4.828427 + + + + + 2.44337811900192,4.42360844529751 2,5 2,6 2.62072936660269,6.08867562380038 3.62072936660269,6.08867562380038 5.17255278310941,5.82264875239923 5.17255278310941,4.82264875239923 3.44337811900192,4.42360844529751 2.44337811900192,4.42360844529751 + 2 + bb + 4.575793 + 8.703307 + + + + + 8.16295585412668,2.73877159309021 8.16295585412668,3.73877159309021 9.16295585412668,3.73877159309021 9.16295585412668,2.73877159309021 8.16295585412668,2.73877159309021 + 3 + cc + 1.000000 + 4.000000 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gfs b/python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gfs new file mode 100644 index 00000000000..48dfb632105 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gfs @@ -0,0 +1,31 @@ + + + mbg_hull_nofield + mbg_hull_nofield + + 3 + EPSG:4326 + + 1 + -1.00000 + 9.16296 + -3.00000 + 6.08868 + + + id + id + Integer + + + area + area + Real + + + perimeter + perimeter + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gml b/python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gml new file mode 100644 index 00000000000..4520296dbf7 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_hull_nofield.gml @@ -0,0 +1,22 @@ + + + + + -1-3 + 9.1629558541266826.088675623800385 + + + + + + 6,-3 -1,-1 -1,3 2,6 2.62072936660269,6.08867562380038 3.62072936660269,6.08867562380038 5.17255278310941,5.82264875239923 9.16295585412668,3.73877159309021 9.16295585412668,2.73877159309021 7.24145873320538,-1.05451055662188 6,-3 + 0 + 66.558273 + 30.786042 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_rect_field.gfs b/python/plugins/processing/tests/testdata/expected/mbg_rect_field.gfs new file mode 100644 index 00000000000..e9cc8449a23 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_rect_field.gfs @@ -0,0 +1,52 @@ + + + mbg_rect_field + mbg_rect_field + + 3 + EPSG:4326 + + 4 + -2.05660 + 9.16296 + -3.00000 + 6.08868 + + + id + id + Integer + + + name + name + String + 2 + + + width + width + Real + + + height + height + Real + + + angle + angle + Real + + + area + area + Real + + + perimeter + perimeter + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_rect_field.gml b/python/plugins/processing/tests/testdata/expected/mbg_rect_field.gml new file mode 100644 index 00000000000..0f39371ea18 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_rect_field.gml @@ -0,0 +1,62 @@ + + + + + -2.056603773584904-3 + 9.1629558541266816.088675623800386 + + + + + + 6,-3 7.35849056603774,1.75471698113208 -0.698113207547169,4.05660377358491 -2.0566037735849,-0.698113207547172 6,-3 + 0 + aa + 4.944980 + 8.378994 + 105.945396 + 41.433962 + 26.647949 + + + + + 5.24145873320538,-0.054510556621882 5.24145873320538,-1.05451055662188 7.24145873320538,-1.05451055662188 7.24145873320538,-0.054510556621882 5.24145873320538,-0.054510556621882 + 1 + dd + 1.000000 + 2.000000 + 90.000000 + 2.000000 + 6.000000 + + + + + 2.0,6.08867562380039 2.0,4.4236084452975 5.1725527831094,4.4236084452975 5.1725527831094,6.08867562380039 2.0,6.08867562380039 + 2 + bb + 1.665067 + 3.172553 + 90.000000 + 5.282514 + 9.675240 + + + + + 8.16295585412668,2.73877159309021 9.16295585412668,2.73877159309021 9.16295585412668,3.73877159309021 8.16295585412668,3.73877159309021 8.16295585412668,2.73877159309021 + 3 + cc + 1.000000 + 1.000000 + 0.000000 + 1.000000 + 4.000000 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gfs b/python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gfs new file mode 100644 index 00000000000..a193607f964 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gfs @@ -0,0 +1,46 @@ + + + mbg_rect_nofield + mbg_rect_nofield + + 3 + EPSG:4326 + + 1 + -2.05660 + 9.67640 + -3.40238 + 7.24010 + + + id + id + Integer + + + width + width + Real + + + height + height + Real + + + angle + angle + Real + + + area + area + Real + + + perimeter + perimeter + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gml b/python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gml new file mode 100644 index 00000000000..393e28a1665 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mbg_rect_nofield.gml @@ -0,0 +1,25 @@ + + + + + -2.056603773584904-3.402382935573825 + 9.6764024191504037.240104298699887 + + + + + + 7.40834027450839,-3.40238293557382 9.6764024191504,4.53583457067323 0.211458371057112,7.24010429869989 -2.0566037735849,-0.698113207547172 7.40834027450839,-3.40238293557382 + 0 + 8.255871 + 9.843690 + 105.945396 + 81.268236 + 36.199122 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index f4e544cafe8..4ae0e6f4dbb 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -3269,3 +3269,155 @@ tests: compare: geometry: precision: 7 + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (hull, no field) + params: + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 3 + results: + OUTPUT: + name: expected/mbg_hull_nofield.gml + type: vector + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (hull, field) + params: + FIELD: name + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 3 + results: + OUTPUT: + name: expected/mbg_hull_field.gml + type: vector + pk: name + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (circle, field) + params: + FIELD: name + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 2 + results: + OUTPUT: + name: expected/mbg_circle_field.gml + type: vector + pk: name + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (circle, no field) + params: + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 2 + results: + OUTPUT: + name: expected/mbg_circle_nofield.gml + type: vector + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (oriented rect, no field) + params: + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 1 + results: + OUTPUT: + name: expected/mbg_rect_nofield.gml + type: vector + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (oriented rect, field) + params: + FIELD: name + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 1 + results: + OUTPUT: + name: expected/mbg_rect_field.gml + type: vector + pk: name + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (envelope, field) + params: + FIELD: name + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 0 + results: + OUTPUT: + name: expected/mbg_env_field.gml + type: vector + pk: name + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip + + - algorithm: qgis:minimumboundinggeometry + name: Minimum enclosing geom (envelope, no field) + params: + INPUT: + name: dissolve_polys.gml + type: vector + TYPE: 0 + results: + OUTPUT: + name: expected/mbg_env_nofield.gml + type: vector + compare: + geometry: + precision: 7 + fields: + fid: skip + id: skip From b6e35428e216f25eaf24d12b0fa29a8454a2ce3f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 18:31:31 +1000 Subject: [PATCH 2/7] 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. --- .../algs/qgis/MinimumBoundingGeometry.py | 83 ++++++++++++++----- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py b/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py index de4f9041b27..a759a503ff8 100644 --- a/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py +++ b/python/plugins/processing/algs/qgis/MinimumBoundingGeometry.py @@ -38,6 +38,7 @@ from qgis.core import (QgsField, QgsWkbTypes, QgsFeatureRequest, QgsFields, + QgsRectangle, QgsProcessingParameterFeatureSource, QgsProcessingParameterField, QgsProcessingParameterEnum, @@ -53,7 +54,6 @@ pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class MinimumBoundingGeometry(QgisAlgorithm): - INPUT = 'INPUT' OUTPUT = 'OUTPUT' TYPE = 'TYPE' @@ -76,11 +76,13 @@ class MinimumBoundingGeometry(QgisAlgorithm): 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' @@ -89,7 +91,9 @@ class MinimumBoundingGeometry(QgisAlgorithm): 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) @@ -108,13 +112,13 @@ class MinimumBoundingGeometry(QgisAlgorithm): 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)) @@ -134,6 +138,7 @@ class MinimumBoundingGeometry(QgisAlgorithm): 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): @@ -143,29 +148,56 @@ class MinimumBoundingGeometry(QgisAlgorithm): 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 + 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 - feature = self.createFeature(feedback, current, type, geometries, group) - sink.addFeature(feature, QgsFeatureSink.FastInsert) - geometry_dict[group] = None + # 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 + 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 @@ -173,11 +205,20 @@ class MinimumBoundingGeometry(QgisAlgorithm): 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} From 85cd1c1673bfa150c8f9ef785dfeda2ad6d59c35 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 19:26:04 +1000 Subject: [PATCH 3/7] [FEATURE] Split minimum enclosing geometry algs into separate feature based algorithms Instead of algorithms which handle both whole layers/groups of features/individual features, we leave the whole layer and group of features handling to the "Minimum bounding geometry" algorithm. The feature-by-feature algorithms are now native c++ algorithms. This affects: - bounding boxes - convex hulls - minimum enclosing circle - minimum oriented rectangles --- .../expected/convex_hull_by_feature.gfs | 42 +++++ .../expected/convex_hull_by_feature.gml | 71 +++++++ .../expected/enclosing_circles_each.gfs | 17 +- .../expected/enclosing_circles_each.gml | 46 ++--- .../testdata/expected/oriented_bounds.gfs | 30 +-- .../testdata/expected/oriented_bounds.gml | 60 +++--- .../tests/testdata/qgis_algorithm_tests.yaml | 28 ++- src/core/processing/qgsnativealgorithms.cpp | 175 ++++++++++++++++++ src/core/processing/qgsnativealgorithms.h | 101 ++++++++++ 9 files changed, 479 insertions(+), 91 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gfs create mode 100644 python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gml diff --git a/python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gfs b/python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gfs new file mode 100644 index 00000000000..4168642000e --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gfs @@ -0,0 +1,42 @@ + + + convex_hull_by_feature + convex_hull_by_feature + + 3 + EPSG:4326 + + 6 + -1.00000 + 10.00000 + -3.00000 + 6.00000 + + + intval + intval + Integer + + + floatval + floatval + Real + + + area + area + Real + + + perimeter + perimeter + Real + + + name + name + String + 5 + + + diff --git a/python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gml b/python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gml new file mode 100644 index 00000000000..3f176f9dcb5 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/convex_hull_by_feature.gml @@ -0,0 +1,71 @@ + + + + + -1-3 + 106 + + + + + + 6,-3 2.4,-1.0 3.8,2.2 6,1 6,-3 + 120 + -100291.43213 + 11.560000 + 14.117095 + + + + + 5.2,3.8 4,4 5.4,5.0 6,4 5.2,3.8 + -33 + 0 + Aaaaa + 1.200000 + 4.927829 + + + + + -1,-1 -1,3 3,3 3,2 2,-1 -1,-1 + 33 + 44.12346 + aaaaa + 14.500000 + 15.162278 + + + + + 6.4,-3.0 6.8,1.8 10,1 9.6,-2.2 6.4,-3.0 + 0 + ASDF + 12.800000 + 14.638510 + + + + + 1.6,4.8 2,6 3,6 1.6,4.8 + 0.123 + bbaaa + 0.600000 + 4.108820 + + + + + 6,-3 2.4,-1.0 3.8,2.2 6,1 6,-3 + 2 + 3.33 + elim + 11.560000 + 14.117095 + + + diff --git a/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gfs b/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gfs index 9d8dc0c421c..8559400e5d0 100644 --- a/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gfs +++ b/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gfs @@ -7,7 +7,7 @@ EPSG:4326 6 - -1.81766 + -1.82843 10.59982 -3.43247 6.32190 @@ -22,21 +22,16 @@ floatval Real - - center_x - center_x - Real - - - center_y - center_y - Real - radius radius Real + + area + area + Real + name name diff --git a/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gml b/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gml index 0a23fc8539b..2df57ab1c09 100644 --- a/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gml +++ b/python/plugins/processing/tests/testdata/expected/enclosing_circles_each.gml @@ -6,72 +6,66 @@ xmlns:gml="http://www.opengis.net/gml"> - -1.817664105611035-3.43247280352443 + -1.82842712474619-3.43247280352443 10.599819742299946.321903675995209 - + - 3.8,2.2 4.26819673362059,2.35151315326536 4.75559048978224,2.4194229717016 5.24737205583712,2.40166604983954 5.72859889775413,2.29878192276454 6.18464918145415,2.11389667261588 6.60166604983954,1.85262794416288 6.96697865638513,1.52291425551124 7.26948716239812,1.13477379024745 7.5,0.7 7.65151315326536,0.23180326637941 7.7194229717016,-0.255590489782239 7.70166604983954,-0.747372055837116 7.59878192276454,-1.22859889775413 7.41389667261588,-1.68464918145415 7.15262794416288,-2.10166604983954 6.82291425551124,-2.46697865638513 6.43477379024745,-2.76948716239812 6.0,-3.0 5.53180326637941,-3.15151315326536 5.04440951021776,-3.2194229717016 4.55262794416288,-3.20166604983954 4.07140110224587,-3.09878192276454 3.61535081854585,-2.91389667261588 3.19833395016046,-2.65262794416288 2.83302134361487,-2.32291425551124 2.53051283760188,-1.93477379024745 2.3,-1.5 2.14848684673464,-1.03180326637941 2.0805770282984,-0.544409510217762 2.09833395016046,-0.052627944162882 2.20121807723546,0.428598897754127 2.38610332738412,0.884649181454149 2.64737205583712,1.30166604983954 2.97708574448876,1.66697865638513 3.36522620975255,1.96948716239812 3.8,2.2 + 3.8,2.2 4.03079076324299,2.28597753206096 4.26819673362059,2.35151315326536 4.51041110834858,2.39610809796435 4.75559048978224,2.4194229717016 5.00186891478551,2.42128033421006 5.24737205583712,2.40166604983954 5.49023148579483,2.36072939513753 5.72859889775413,2.29878192276454 5.96066017177982,2.21629509039023 6.18464918145415,2.11389667261588 6.39886123516523,1.99236598323061 6.60166604983954,1.85262794416288 6.79152015838052,1.69574604626613 6.96697865638513,1.52291425551124 7.12670619873881,1.33544792618453 7.26948716239812,1.13477379024745 7.39423489801611,0.922419099044831 7.5,0.7 7.58597753206096,0.469209236757009 7.65151315326536,0.23180326637941 7.69610809796435,-0.010411108348578 7.7194229717016,-0.255590489782239 7.72128033421006,-0.501868914785503 7.70166604983954,-0.747372055837116 7.66072939513753,-0.990231485794827 7.59878192276454,-1.22859889775413 7.51629509039023,-1.46066017177982 7.41389667261588,-1.68464918145415 7.29236598323061,-1.89886123516523 7.15262794416288,-2.10166604983954 6.99574604626613,-2.29152015838052 6.82291425551124,-2.46697865638513 6.63544792618453,-2.6267061987388 6.43477379024745,-2.76948716239812 6.22241909904483,-2.89423489801611 6.0,-3.0 5.76920923675701,-3.08597753206096 5.53180326637941,-3.15151315326536 5.28958889165142,-3.19610809796435 5.04440951021776,-3.2194229717016 4.7981310852145,-3.22128033421006 4.55262794416288,-3.20166604983954 4.30976851420517,-3.16072939513753 4.07140110224587,-3.09878192276454 3.83933982822018,-3.01629509039023 3.61535081854585,-2.91389667261588 3.40113876483477,-2.79236598323061 3.19833395016046,-2.65262794416288 3.00847984161948,-2.49574604626613 2.83302134361487,-2.32291425551124 2.67329380126119,-2.13544792618453 2.53051283760188,-1.93477379024745 2.40576510198389,-1.72241909904483 2.3,-1.5 2.21402246793904,-1.26920923675701 2.14848684673464,-1.03180326637941 2.10389190203565,-0.789588891651422 2.0805770282984,-0.544409510217762 2.07871966578994,-0.298131085214498 2.09833395016046,-0.052627944162882 2.13927060486247,0.190231485794829 2.20121807723546,0.428598897754127 2.28370490960977,0.660660171779822 2.38610332738412,0.884649181454149 2.50763401676939,1.09886123516523 2.64737205583712,1.30166604983954 2.80425395373387,1.49152015838052 2.97708574448876,1.66697865638513 3.16455207381547,1.82670619873881 3.36522620975255,1.96948716239812 3.57758090095517,2.09423489801611 3.8,2.2 120 -100291.43213 - 4.9 - -0.4 - 2.82311884269862 + 2.823119 + 25.038493 - 5.0,5.08319489631876 5.17420296559052,5.06795411167699 5.34311286222252,5.02269484128082 5.50159744815938,4.94879226515894 5.64484124945447,4.8484918756903 5.7684918756903,4.72484124945447 5.86879226515894,4.58159744815938 5.94269484128082,4.42311286222252 5.98795411167699,4.25420296559052 6.00319489631876,4.08 5.98795411167699,3.90579703440948 5.94269484128082,3.73688713777748 5.86879226515894,3.57840255184062 5.7684918756903,3.43515875054553 5.64484124945447,3.3115081243097 5.50159744815938,3.21120773484106 5.34311286222252,3.13730515871918 5.17420296559052,3.09204588832301 5.0,3.07680510368124 4.82579703440948,3.09204588832301 4.65688713777748,3.13730515871918 4.49840255184062,3.21120773484106 4.35515875054553,3.3115081243097 4.2315081243097,3.43515875054553 4.13120773484106,3.57840255184062 4.05730515871918,3.73688713777748 4.01204588832301,3.90579703440948 3.99680510368124,4.08 4.01204588832301,4.25420296559052 4.05730515871918,4.42311286222252 4.13120773484106,4.58159744815938 4.2315081243097,4.72484124945447 4.35515875054553,4.8484918756903 4.49840255184062,4.94879226515894 4.65688713777748,5.02269484128082 4.82579703440948,5.06795411167699 5.0,5.08319489631876 + 5.0,5.08319489631876 5.08743419630932,5.07937743686544 5.17420296559052,5.06795411167699 5.25964594511694,5.04901185915567 5.34311286222252,5.02269484128082 5.42396848326937,4.98920334644911 5.50159744815938,4.94879226515894 5.57540895359607,4.90176915013979 5.64484124945447,4.8484918756903 5.70936591403873,4.78936591403873 5.7684918756903,4.72484124945447 5.82176915013979,4.65540895359607 5.86879226515894,4.58159744815938 5.90920334644911,4.50396848326937 5.94269484128082,4.42311286222252 5.96901185915567,4.33964594511694 5.98795411167699,4.25420296559052 5.99937743686544,4.16743419630932 6.00319489631876,4.08 5.99937743686544,3.99256580369068 5.98795411167699,3.90579703440948 5.96901185915567,3.82035405488306 5.94269484128082,3.73688713777748 5.90920334644911,3.65603151673063 5.86879226515894,3.57840255184062 5.82176915013979,3.50459104640393 5.7684918756903,3.43515875054553 5.70936591403873,3.37063408596127 5.64484124945447,3.3115081243097 5.57540895359607,3.25823084986021 5.50159744815938,3.21120773484106 5.42396848326937,3.17079665355089 5.34311286222252,3.13730515871918 5.25964594511694,3.11098814084433 5.17420296559052,3.09204588832301 5.08743419630932,3.08062256313456 5.0,3.07680510368124 4.91256580369068,3.08062256313456 4.82579703440948,3.09204588832301 4.74035405488306,3.11098814084433 4.65688713777748,3.13730515871918 4.57603151673063,3.17079665355089 4.49840255184062,3.21120773484106 4.42459104640393,3.25823084986021 4.35515875054553,3.3115081243097 4.29063408596127,3.37063408596127 4.2315081243097,3.43515875054553 4.17823084986021,3.50459104640393 4.13120773484106,3.57840255184062 4.09079665355089,3.65603151673063 4.05730515871918,3.73688713777748 4.03098814084433,3.82035405488306 4.01204588832301,3.90579703440948 4.00062256313456,3.99256580369068 3.99680510368124,4.08 4.00062256313456,4.16743419630932 4.01204588832301,4.25420296559052 4.03098814084433,4.33964594511694 4.05730515871918,4.42311286222252 4.09079665355089,4.50396848326937 4.13120773484106,4.58159744815938 4.17823084986021,4.65540895359607 4.2315081243097,4.72484124945447 4.29063408596127,4.78936591403873 4.35515875054553,4.8484918756903 4.42459104640393,4.90176915013979 4.49840255184062,4.94879226515894 4.57603151673063,4.98920334644911 4.65688713777748,5.02269484128082 4.74035405488306,5.04901185915567 4.82579703440948,5.06795411167699 4.91256580369068,5.07937743686544 5.0,5.08319489631876 -33 0 Aaaaa - 5 - 4.08 - 1.00319489631876 + 1.003195 + 3.161699 - 3.0,3.0 3.31691186135828,2.62231915069056 3.56342552822315,2.19534495492048 3.73205080756888,1.73205080756888 3.81766410561104,1.24651366686488 3.81766410561104,0.753486333135122 3.73205080756888,0.267949192431123 3.56342552822315,-0.195344954920481 3.31691186135828,-0.622319150690556 3.0,-1.0 2.62231915069056,-1.31691186135828 2.19534495492048,-1.56342552822315 1.73205080756888,-1.73205080756888 1.24651366686488,-1.81766410561103 0.753486333135123,-1.81766410561103 0.267949192431122,-1.73205080756888 -0.195344954920479,-1.56342552822315 -0.622319150690555,-1.31691186135828 -1.0,-1.0 -1.31691186135828,-0.622319150690556 -1.56342552822315,-0.195344954920479 -1.73205080756888,0.267949192431122 -1.81766410561103,0.753486333135123 -1.81766410561103,1.24651366686488 -1.73205080756888,1.73205080756888 -1.56342552822316,2.19534495492048 -1.31691186135828,2.62231915069056 -1.0,3.0 -0.622319150690556,3.31691186135828 -0.195344954920481,3.56342552822315 0.267949192431124,3.73205080756888 0.753486333135123,3.81766410561104 1.24651366686488,3.81766410561104 1.73205080756888,3.73205080756888 2.19534495492048,3.56342552822316 2.62231915069056,3.31691186135828 3.0,3.0 + 3.0,3.0 3.16670088167881,2.81807791068817 3.31691186135828,2.62231915069056 3.44948974278318,2.4142135623731 3.56342552822315,2.19534495492048 3.6578520975547,1.9673790505919 3.73205080756888,1.73205080756888 3.78545696128008,1.49115121587589 3.81766410561104,1.24651366686488 3.82842712474619,1.0 3.81766410561104,0.753486333135122 3.78545696128008,0.508848784124109 3.73205080756888,0.267949192431123 3.6578520975547,0.032620949408098 3.56342552822315,-0.195344954920481 3.44948974278318,-0.414213562373096 3.31691186135828,-0.622319150690556 3.16670088167881,-0.818077910688175 3.0,-1.0 2.81807791068817,-1.16670088167881 2.62231915069056,-1.31691186135828 2.4142135623731,-1.44948974278318 2.19534495492048,-1.56342552822315 1.9673790505919,-1.6578520975547 1.73205080756888,-1.73205080756888 1.49115121587589,-1.78545696128008 1.24651366686488,-1.81766410561103 1.0,-1.82842712474619 0.753486333135123,-1.81766410561103 0.508848784124109,-1.78545696128008 0.267949192431122,-1.73205080756888 0.032620949408099,-1.6578520975547 -0.195344954920479,-1.56342552822315 -0.414213562373095,-1.44948974278318 -0.622319150690555,-1.31691186135828 -0.818077910688175,-1.16670088167881 -1.0,-1.0 -1.16670088167881,-0.818077910688175 -1.31691186135828,-0.622319150690556 -1.44948974278318,-0.414213562373095 -1.56342552822315,-0.195344954920479 -1.6578520975547,0.032620949408099 -1.73205080756888,0.267949192431122 -1.78545696128008,0.508848784124108 -1.81766410561103,0.753486333135123 -1.82842712474619,1.0 -1.81766410561103,1.24651366686488 -1.78545696128008,1.49115121587589 -1.73205080756888,1.73205080756888 -1.6578520975547,1.9673790505919 -1.56342552822316,2.19534495492048 -1.44948974278318,2.4142135623731 -1.31691186135828,2.62231915069056 -1.16670088167881,2.81807791068817 -1.0,3.0 -0.818077910688175,3.16670088167881 -0.622319150690556,3.31691186135828 -0.414213562373096,3.44948974278318 -0.195344954920481,3.56342552822315 0.032620949408098,3.6578520975547 0.267949192431124,3.73205080756888 0.508848784124109,3.78545696128008 0.753486333135123,3.81766410561104 1.0,3.82842712474619 1.24651366686488,3.81766410561104 1.49115121587589,3.78545696128008 1.73205080756888,3.73205080756888 1.9673790505919,3.6578520975547 2.19534495492048,3.56342552822316 2.4142135623731,3.44948974278318 2.62231915069056,3.31691186135828 2.81807791068817,3.16670088167881 3.0,3.0 33 44.12346 aaaaa - 1 - 1 - 2.82842712474619 + 2.828427 + 25.132741 - 7.8734693877551,2.02022790556525 8.3468951585034,1.97880851760375 8.80593612677252,1.85580886086324 9.23664456502752,1.65496621767295 9.62593361532103,1.38238309011494 9.96197492684963,1.04634177858634 10.2345580544076,0.657052728292829 10.4354006975979,0.226344290037822 10.5584003543384,-0.232696678231291 10.5998197422999,-0.706122448979591 10.5584003543384,-1.17954821972789 10.4354006975979,-1.638589187997 10.2345580544076,-2.06929762625201 9.96197492684963,-2.45858667654552 9.62593361532103,-2.79462798807412 9.23664456502752,-3.06721111563213 8.80593612677252,-3.26805375882242 8.3468951585034,-3.39105341556293 7.8734693877551,-3.43247280352443 7.4000436170068,-3.39105341556293 6.94100264873769,-3.26805375882242 6.51029421048268,-3.06721111563213 6.12100516018918,-2.79462798807412 5.78496384866057,-2.45858667654552 5.51238072110256,-2.06929762625201 5.31153807791227,-1.63858918799701 5.18853842117176,-1.17954821972789 5.14711903321026,-0.70612244897959 5.18853842117176,-0.23269667823129 5.31153807791227,0.226344290037823 5.51238072110256,0.65705272829283 5.78496384866057,1.04634177858634 6.12100516018918,1.38238309011494 6.51029421048268,1.65496621767295 6.94100264873769,1.85580886086324 7.4000436170068,1.97880851760375 7.8734693877551,2.02022790556525 + 7.8734693877551,2.02022790556525 8.1110864778958,2.00985331935853 8.3468951585034,1.97880851760375 8.57910078313332,1.92732976998763 8.80593612677252,1.85580886086324 9.02567483548898,1.76479010753453 9.23664456502752,1.65496621767295 9.43723970835934,1.52717301739383 9.62593361532103,1.38238309011494 9.80129021134411,1.22169837460941 9.96197492684963,1.04634177858634 10.1067648541285,0.85764787162465 10.2345580544076,0.657052728292829 10.3443819442692,0.446082998754287 10.4354006975979,0.226344290037822 10.5069216067223,-0.000491053601378 10.5584003543384,-0.232696678231291 10.5894451560932,-0.468505358838895 10.5998197422999,-0.706122448979591 10.5894451560932,-0.943739539120288 10.5584003543384,-1.17954821972789 10.5069216067223,-1.4117538443578 10.4354006975979,-1.638589187997 10.3443819442692,-1.85832789671347 10.2345580544076,-2.06929762625201 10.1067648541285,-2.26989276958383 9.96197492684963,-2.45858667654552 9.80129021134411,-2.6339432725686 9.62593361532103,-2.79462798807412 9.43723970835934,-2.93941791535301 9.23664456502752,-3.06721111563213 9.02567483548898,-3.17703500549371 8.80593612677252,-3.26805375882242 8.57910078313332,-3.33957466794681 8.3468951585034,-3.39105341556293 8.1110864778958,-3.42209821731771 7.8734693877551,-3.43247280352443 7.63585229761441,-3.42209821731771 7.4000436170068,-3.39105341556293 7.16783799237689,-3.33957466794681 6.94100264873769,-3.26805375882242 6.72126394002122,-3.17703500549371 6.51029421048268,-3.06721111563213 6.30969906715086,-2.93941791535301 6.12100516018918,-2.79462798807412 5.9456485641661,-2.63394327256859 5.78496384866057,-2.45858667654552 5.64017392138168,-2.26989276958383 5.51238072110256,-2.06929762625201 5.40255683124098,-1.85832789671347 5.31153807791227,-1.63858918799701 5.24001716878788,-1.4117538443578 5.18853842117176,-1.17954821972789 5.15749361941698,-0.943739539120287 5.14711903321026,-0.70612244897959 5.15749361941698,-0.468505358838894 5.18853842117176,-0.23269667823129 5.24001716878788,-0.000491053601377 5.31153807791227,0.226344290037823 5.40255683124098,0.446082998754288 5.51238072110256,0.65705272829283 5.64017392138168,0.85764787162465 5.78496384866057,1.04634177858634 5.9456485641661,1.22169837460941 6.12100516018918,1.38238309011494 6.30969906715086,1.52717301739383 6.51029421048268,1.65496621767295 6.72126394002122,1.76479010753453 6.94100264873769,1.85580886086324 7.16783799237689,1.92732976998763 7.4000436170068,1.97880851760375 7.63585229761441,2.00985331935853 7.8734693877551,2.02022790556525 0 ASDF - 7.8734693877551 - -0.706122448979591 - 2.72635035454484 + 2.726350 + 23.351415 - 3,6 3.0935543337087,5.86933092744047 3.16299692054554,5.72440147214358 3.20621778264911,5.56961524227066 3.22190367599521,5.40967533909081 3.20957799265196,5.24944145562864 3.16961524227066,5.09378221735089 3.10322967279951,4.94742725144526 3.01243837617418,4.81482347949161 2.9,4.7 2.76933092744047,4.6064456662913 2.62440147214358,4.53700307945446 2.46961524227066,4.49378221735089 2.30967533909081,4.47809632400479 2.14944145562864,4.49042200734804 1.99378221735089,4.53038475772934 1.84742725144526,4.59677032720049 1.71482347949161,4.68756162382582 1.6,4.8 1.5064456662913,4.93066907255953 1.43700307945446,5.07559852785642 1.39378221735089,5.23038475772934 1.37809632400479,5.39032466090919 1.39042200734804,5.55055854437136 1.43038475772934,5.70621778264911 1.49677032720049,5.85257274855473 1.58756162382582,5.98517652050839 1.7,6.1 1.83066907255953,6.1935543337087 1.97559852785642,6.26299692054554 2.13038475772934,6.30621778264911 2.29032466090919,6.32190367599521 2.45055854437136,6.30957799265196 2.60621778264911,6.26961524227066 2.75257274855473,6.20322967279951 2.88517652050839,6.11243837617418 3,6 + 3,6 3.04962973431282,5.93670779893169 3.0935543337087,5.86933092744047 3.13143950546386,5.79838216420168 3.16299692054554,5.72440147214358 3.18798640797007,5.6479518890035 3.20621778264911,5.56961524227066 3.21755229281292,5.48998772112766 3.22190367599521,5.40967533909081 3.21923881554251,5.32928932188135 3.20957799265196,5.24944145562864 3.19299473201913,5.17073943080833 3.16961524227066,5.09378221735089 3.13961745544048,5.01915550611876 3.10322967279951,4.94742725144526 3.0607288273452,4.87914334865916 3.01243837617418,4.81482347949161 2.95872583877841,4.75495715698437 2.9,4.7 2.83670779893169,4.65037026568718 2.76933092744047,4.6064456662913 2.69838216420168,4.56856049453614 2.62440147214358,4.53700307945446 2.5479518890035,4.51201359202993 2.46961524227066,4.49378221735089 2.38998772112766,4.48244770718708 2.30967533909081,4.47809632400479 2.22928932188134,4.48076118445749 2.14944145562864,4.49042200734804 2.07073943080833,4.50700526798087 1.99378221735089,4.53038475772934 1.91915550611876,4.56038254455952 1.84742725144526,4.59677032720049 1.77914334865916,4.63927117265479 1.71482347949161,4.68756162382582 1.65495715698437,4.74127416122159 1.6,4.8 1.55037026568718,4.86329220106831 1.5064456662913,4.93066907255953 1.46856049453614,5.00161783579832 1.43700307945446,5.07559852785642 1.41201359202993,5.1520481109965 1.39378221735089,5.23038475772934 1.38244770718708,5.31001227887234 1.37809632400479,5.39032466090919 1.38076118445749,5.47071067811865 1.39042200734804,5.55055854437136 1.40700526798087,5.62926056919167 1.43038475772934,5.70621778264911 1.46038254455952,5.78084449388124 1.49677032720049,5.85257274855473 1.53927117265479,5.92085665134084 1.58756162382582,5.98517652050839 1.64127416122159,6.04504284301563 1.7,6.1 1.76329220106831,6.14962973431282 1.83066907255953,6.1935543337087 1.90161783579832,6.23143950546386 1.97559852785642,6.26299692054554 2.0520481109965,6.28798640797008 2.13038475772934,6.30621778264911 2.21001227887234,6.31755229281292 2.29032466090919,6.32190367599521 2.37071067811865,6.31923881554251 2.45055854437136,6.30957799265196 2.52926056919167,6.29299473201913 2.60621778264911,6.26961524227066 2.68084449388124,6.23961745544048 2.75257274855473,6.20322967279951 2.82085665134084,6.16072882734521 2.88517652050839,6.11243837617418 2.94504284301563,6.05872583877841 3,6 0.123 bbaaa - 2.3 - 5.4 - 0.921954445729289 + 0.921954 + 2.670354 - 3.8,2.2 4.26819673362059,2.35151315326536 4.75559048978224,2.4194229717016 5.24737205583712,2.40166604983954 5.72859889775413,2.29878192276454 6.18464918145415,2.11389667261588 6.60166604983954,1.85262794416288 6.96697865638513,1.52291425551124 7.26948716239812,1.13477379024745 7.5,0.7 7.65151315326536,0.23180326637941 7.7194229717016,-0.255590489782239 7.70166604983954,-0.747372055837116 7.59878192276454,-1.22859889775413 7.41389667261588,-1.68464918145415 7.15262794416288,-2.10166604983954 6.82291425551124,-2.46697865638513 6.43477379024745,-2.76948716239812 6.0,-3.0 5.53180326637941,-3.15151315326536 5.04440951021776,-3.2194229717016 4.55262794416288,-3.20166604983954 4.07140110224587,-3.09878192276454 3.61535081854585,-2.91389667261588 3.19833395016046,-2.65262794416288 2.83302134361487,-2.32291425551124 2.53051283760188,-1.93477379024745 2.3,-1.5 2.14848684673464,-1.03180326637941 2.0805770282984,-0.544409510217762 2.09833395016046,-0.052627944162882 2.20121807723546,0.428598897754127 2.38610332738412,0.884649181454149 2.64737205583712,1.30166604983954 2.97708574448876,1.66697865638513 3.36522620975255,1.96948716239812 3.8,2.2 + 3.8,2.2 4.03079076324299,2.28597753206096 4.26819673362059,2.35151315326536 4.51041110834858,2.39610809796435 4.75559048978224,2.4194229717016 5.00186891478551,2.42128033421006 5.24737205583712,2.40166604983954 5.49023148579483,2.36072939513753 5.72859889775413,2.29878192276454 5.96066017177982,2.21629509039023 6.18464918145415,2.11389667261588 6.39886123516523,1.99236598323061 6.60166604983954,1.85262794416288 6.79152015838052,1.69574604626613 6.96697865638513,1.52291425551124 7.12670619873881,1.33544792618453 7.26948716239812,1.13477379024745 7.39423489801611,0.922419099044831 7.5,0.7 7.58597753206096,0.469209236757009 7.65151315326536,0.23180326637941 7.69610809796435,-0.010411108348578 7.7194229717016,-0.255590489782239 7.72128033421006,-0.501868914785503 7.70166604983954,-0.747372055837116 7.66072939513753,-0.990231485794827 7.59878192276454,-1.22859889775413 7.51629509039023,-1.46066017177982 7.41389667261588,-1.68464918145415 7.29236598323061,-1.89886123516523 7.15262794416288,-2.10166604983954 6.99574604626613,-2.29152015838052 6.82291425551124,-2.46697865638513 6.63544792618453,-2.6267061987388 6.43477379024745,-2.76948716239812 6.22241909904483,-2.89423489801611 6.0,-3.0 5.76920923675701,-3.08597753206096 5.53180326637941,-3.15151315326536 5.28958889165142,-3.19610809796435 5.04440951021776,-3.2194229717016 4.7981310852145,-3.22128033421006 4.55262794416288,-3.20166604983954 4.30976851420517,-3.16072939513753 4.07140110224587,-3.09878192276454 3.83933982822018,-3.01629509039023 3.61535081854585,-2.91389667261588 3.40113876483477,-2.79236598323061 3.19833395016046,-2.65262794416288 3.00847984161948,-2.49574604626613 2.83302134361487,-2.32291425551124 2.67329380126119,-2.13544792618453 2.53051283760188,-1.93477379024745 2.40576510198389,-1.72241909904483 2.3,-1.5 2.21402246793904,-1.26920923675701 2.14848684673464,-1.03180326637941 2.10389190203565,-0.789588891651422 2.0805770282984,-0.544409510217762 2.07871966578994,-0.298131085214498 2.09833395016046,-0.052627944162882 2.13927060486247,0.190231485794829 2.20121807723546,0.428598897754127 2.28370490960977,0.660660171779822 2.38610332738412,0.884649181454149 2.50763401676939,1.09886123516523 2.64737205583712,1.30166604983954 2.80425395373387,1.49152015838052 2.97708574448876,1.66697865638513 3.16455207381547,1.82670619873881 3.36522620975255,1.96948716239812 3.57758090095517,2.09423489801611 3.8,2.2 2 3.33 elim - 4.9 - -0.4 - 2.82311884269862 + 2.823119 + 25.038493 diff --git a/python/plugins/processing/tests/testdata/expected/oriented_bounds.gfs b/python/plugins/processing/tests/testdata/expected/oriented_bounds.gfs index 2867f28fd42..4f4d91bbfc2 100644 --- a/python/plugins/processing/tests/testdata/expected/oriented_bounds.gfs +++ b/python/plugins/processing/tests/testdata/expected/oriented_bounds.gfs @@ -22,21 +22,6 @@ floatval Real - - area - area - Real - - - perimeter - perimeter - Real - - - angle - angle - Real - width width @@ -47,6 +32,21 @@ height Real + + angle + angle + Real + + + area + area + Real + + + perimeter + perimeter + Real + name name diff --git a/python/plugins/processing/tests/testdata/expected/oriented_bounds.gml b/python/plugins/processing/tests/testdata/expected/oriented_bounds.gml index 8f543a5b0ff..f9a6aea471d 100644 --- a/python/plugins/processing/tests/testdata/expected/oriented_bounds.gml +++ b/python/plugins/processing/tests/testdata/expected/oriented_bounds.gml @@ -16,11 +16,11 @@ 6,-3 7.69811320754717,0.056603773584906 3.80943396226415,2.21698113207547 2.11132075471698,-0.839622641509436 6,-3 120 -100291.43213 - 15.5547169811321 - 15.8902367081648 - 119.054604099077 - 3.49662910448615 - 4.44848924959627 + 3.496629 + 4.448489 + 119.054604 + 15.554717 + 15.890237 @@ -29,11 +29,11 @@ -33 0 Aaaaa - 2.16470588235294 - 6.11189775091559 - 165.963756532073 - 1.94028500029066 - 1.11566387516713 + 1.940285 + 1.115664 + 165.963757 + 2.164706 + 6.111898 @@ -42,11 +42,11 @@ 33 44.12346 aaaaa - 16 - 16 - 90 - 4 - 4 + 4.000000 + 4.000000 + 90.000000 + 16.000000 + 16.000000 @@ -54,11 +54,11 @@ 6.4,-3.0 9.64413793103449,-3.27034482758621 10.0441379310345,1.52965517241379 6.8,1.8 6.4,-3.0 0 ASDF - 15.68 - 16.1440412835671 - 4.76364169072617 - 3.25538281026661 - 4.81663783151692 + 3.255383 + 4.816638 + 4.763642 + 15.680000 + 16.144041 @@ -66,11 +66,11 @@ 1.36470588235294,4.94117647058824 2.1,4.5 3.0,6.0 2.26470588235294,6.44117647058824 1.36470588235294,4.94117647058824 0.123 bbaaa - 1.5 - 5.21355698833227 - 30.9637565320735 - 0.857492925712544 - 1.74928556845359 + 0.857493 + 1.749286 + 30.963757 + 1.500000 + 5.213557 @@ -79,11 +79,11 @@ 2 3.33 elim - 15.5547169811321 - 15.8902367081648 - 119.054604099077 - 3.49662910448615 - 4.44848924959627 + 3.496629 + 4.448489 + 119.054604 + 15.554717 + 15.890237 diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 4ae0e6f4dbb..db4f901cb4e 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -610,7 +610,7 @@ tests: name: expected/multi_to_single.gml type: vector - - algorithm: qgis:boundingboxes + - algorithm: native:boundingboxes name: Bounding boxes for lines params: INPUT: @@ -621,7 +621,7 @@ tests: name: expected/lines_bounds.gml type: vector - - algorithm: qgis:boundingboxes + - algorithm: native:boundingboxes name: Bounding boxes for multilines params: INPUT: @@ -632,7 +632,7 @@ tests: name: expected/multiline_bounds.gml type: vector - - algorithm: qgis:boundingboxes + - algorithm: native:boundingboxes name: Bounding boxes for multipolygons params: INPUT: @@ -643,7 +643,7 @@ tests: name: expected/multipoly_bounds.gml type: vector - - algorithm: qgis:boundingboxes + - algorithm: native:boundingboxes name: Bounding boxes for points params: INPUT: @@ -654,7 +654,7 @@ tests: name: expected/point_bounds.gml type: vector - - algorithm: qgis:boundingboxes + - algorithm: native:boundingboxes name: Bounding boxes for polygons params: INPUT: @@ -665,7 +665,7 @@ tests: name: expected/poly_bounds.gml type: vector - - algorithm: qgis:boundingboxes + - algorithm: native:boundingboxes name: Bounding boxes for multipoints params: INPUT: @@ -2241,10 +2241,9 @@ tests: hash: fe6e018be13c5a3c17f3f4d0f0dc7686c628cb440b74c4642aa0c939 type: rasterhash - - algorithm: qgis:orientedminimumboundingbox + - algorithm: native:orientedminimumboundingbox name: Oriented minimum bounding box polys params: - BY_FEATURE: true INPUT: name: custom/oriented_bbox.gml type: vector @@ -3240,7 +3239,7 @@ tests: name: expected/raster_extent.gml type: vector - - algorithm: qgis:minimalenclosingcircle + - algorithm: native:minimumenclosingcircle name: Minimal enclosing circle each features params: BY_FEATURE: true @@ -3421,3 +3420,14 @@ tests: fields: fid: skip id: skip + + - algorithm: native:convexhull + name: Convex hull by feature + params: + INPUT: + name: custom/oriented_bbox.gml + type: vector + results: + OUTPUT: + name: expected/convex_hull_by_feature.gml + type: vector diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 1854fb6214a..8a2f17df3ae 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -68,6 +68,10 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsSubdivideAlgorithm() ); addAlgorithm( new QgsTransformAlgorithm() ); addAlgorithm( new QgsRemoveNullGeometryAlgorithm() ); + addAlgorithm( new QgsBoundingBoxAlgorithm() ); + addAlgorithm( new QgsOrientedMinimumBoundingBoxAlgorithm() ); + addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() ); + addAlgorithm( new QgsConvexHullAlgorithm() ); } void QgsCentroidAlgorithm::initAlgorithm( const QVariantMap & ) @@ -1087,4 +1091,175 @@ QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap } +QString QgsBoundingBoxAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm calculates the bounding box (envelope) of each feature in an input layer." ); +} + +QgsBoundingBoxAlgorithm *QgsBoundingBoxAlgorithm::createInstance() const +{ + return new QgsBoundingBoxAlgorithm(); +} + +QgsFields QgsBoundingBoxAlgorithm::outputFields( const QgsFields &inputFields ) const +{ + QgsFields fields = inputFields; + fields.append( QgsField( QStringLiteral( "width" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "height" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "perimeter" ), QVariant::Double, QString(), 20, 6 ) ); + return fields; +} + +QgsFeature QgsBoundingBoxAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * ) +{ + QgsFeature f = feature; + if ( f.hasGeometry() ) + { + QgsRectangle bounds = f.geometry().boundingBox(); + QgsGeometry outputGeometry = QgsGeometry::fromRect( bounds ); + f.setGeometry( outputGeometry ); + QgsAttributes attrs = f.attributes(); + attrs << bounds.width() + << bounds.height() + << bounds.area() + << bounds.perimeter(); + f.setAttributes( attrs ); + } + return f; +} + +QString QgsOrientedMinimumBoundingBoxAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm calculates the minimum area rotated rectangle which covers each feature in an input layer." ); +} + +QgsOrientedMinimumBoundingBoxAlgorithm *QgsOrientedMinimumBoundingBoxAlgorithm::createInstance() const +{ + return new QgsOrientedMinimumBoundingBoxAlgorithm(); +} + +QgsFields QgsOrientedMinimumBoundingBoxAlgorithm::outputFields( const QgsFields &inputFields ) const +{ + QgsFields fields = inputFields; + fields.append( QgsField( QStringLiteral( "width" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "height" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "angle" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "perimeter" ), QVariant::Double, QString(), 20, 6 ) ); + return fields; +} + +QgsFeature QgsOrientedMinimumBoundingBoxAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * ) +{ + QgsFeature f = feature; + if ( f.hasGeometry() ) + { + double area = 0; + double angle = 0; + double width = 0; + double height = 0; + QgsGeometry outputGeometry = f.geometry().orientedMinimumBoundingBox( area, angle, width, height ); + f.setGeometry( outputGeometry ); + QgsAttributes attrs = f.attributes(); + attrs << width + << height + << angle + << area + << 2 * width + 2 * height; + f.setAttributes( attrs ); + } + return f; +} + + +void QgsMinimumEnclosingCircleAlgorithm::initParameters( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterNumber( QStringLiteral( "SEGMENTS" ), QObject::tr( "Number of segments in circles" ), QgsProcessingParameterNumber::Integer, + 72, false, 8, 100000 ) ); +} + +QString QgsMinimumEnclosingCircleAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm calculates the minimum enclosing circle which covers each feature in an input layer." ); +} + +QgsMinimumEnclosingCircleAlgorithm *QgsMinimumEnclosingCircleAlgorithm::createInstance() const +{ + return new QgsMinimumEnclosingCircleAlgorithm(); +} + +QgsFields QgsMinimumEnclosingCircleAlgorithm::outputFields( const QgsFields &inputFields ) const +{ + QgsFields fields = inputFields; + fields.append( QgsField( QStringLiteral( "radius" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) ); + return fields; +} + +bool QgsMinimumEnclosingCircleAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +{ + mSegments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context ); + return true; +} + +QgsFeature QgsMinimumEnclosingCircleAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback * ) +{ + QgsFeature f = feature; + if ( f.hasGeometry() ) + { + double radius = 0; + QgsPointXY center; + QgsGeometry outputGeometry = f.geometry().minimalEnclosingCircle( center, radius, mSegments ); + f.setGeometry( outputGeometry ); + QgsAttributes attrs = f.attributes(); + attrs << radius + << M_PI *radius *radius; + f.setAttributes( attrs ); + } + return f; +} + +QString QgsConvexHullAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm calculates the convex hull for each feature in an input layer." ); +} + +QgsConvexHullAlgorithm *QgsConvexHullAlgorithm::createInstance() const +{ + return new QgsConvexHullAlgorithm(); +} + +QgsFields QgsConvexHullAlgorithm::outputFields( const QgsFields &inputFields ) const +{ + QgsFields fields = inputFields; + fields.append( QgsField( QStringLiteral( "area" ), QVariant::Double, QString(), 20, 6 ) ); + fields.append( QgsField( QStringLiteral( "perimeter" ), QVariant::Double, QString(), 20, 6 ) ); + return fields; +} + +QgsFeature QgsConvexHullAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) +{ + QgsFeature f = feature; + if ( f.hasGeometry() ) + { + QgsGeometry outputGeometry = f.geometry().convexHull(); + if ( !outputGeometry ) + feedback->reportError( outputGeometry.lastError() ); + f.setGeometry( outputGeometry ); + if ( outputGeometry ) + { + QgsAttributes attrs = f.attributes(); + attrs << outputGeometry.geometry()->area() + << outputGeometry.geometry()->perimeter(); + f.setAttributes( attrs ); + } + } + return f; +} + ///@endcond + + + + diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index c23fbeb5140..89fdaa43b9a 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -323,6 +323,107 @@ class QgsRemoveNullGeometryAlgorithm : public QgsProcessingAlgorithm }; +/** + * Native bounding boxes algorithm. + */ +class QgsBoundingBoxAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsBoundingBoxAlgorithm() = default; + QString name() const override { return QStringLiteral( "boundingboxes" ); } + QString displayName() const override { return QObject::tr( "Bounding boxes" ); } + virtual QStringList tags() const override { return QObject::tr( "bounding,boxes,envelope,rectangle,extent" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry" ); } + QString shortHelpString() const override; + QgsBoundingBoxAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + QString outputName() const override { return QObject::tr( "Bounds" ); } + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type ) const override { return QgsWkbTypes::Polygon; } + QgsFields outputFields( const QgsFields &inputFields ) const override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + +}; + +/** + * Native minimum oriented bounding box algorithm. + */ +class QgsOrientedMinimumBoundingBoxAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsOrientedMinimumBoundingBoxAlgorithm() = default; + QString name() const override { return QStringLiteral( "orientedminimumboundingbox" ); } + QString displayName() const override { return QObject::tr( "Oriented minimum bounding box" ); } + virtual QStringList tags() const override { return QObject::tr( "bounding,boxes,envelope,rectangle,extent,oriented,angle" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry" ); } + QString shortHelpString() const override; + QgsOrientedMinimumBoundingBoxAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + QString outputName() const override { return QObject::tr( "Bounding boxes" ); } + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type ) const override { return QgsWkbTypes::Polygon; } + QgsFields outputFields( const QgsFields &inputFields ) const override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + +}; + +/** + * Native minimum enclosing circle algorithm. + */ +class QgsMinimumEnclosingCircleAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsMinimumEnclosingCircleAlgorithm() = default; + void initParameters( const QVariantMap &configuration = QVariantMap() ) override; + QString name() const override { return QStringLiteral( "minimumenclosingcircle" ); } + QString displayName() const override { return QObject::tr( "Minimum enclosing circles" ); } + virtual QStringList tags() const override { return QObject::tr( "minimum,circle,ellipse,extent,bounds,bounding" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry" ); } + QString shortHelpString() const override; + QgsMinimumEnclosingCircleAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + QString outputName() const override { return QObject::tr( "Minimum enclosing circles" ); } + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type ) const override { return QgsWkbTypes::Polygon; } + QgsFields outputFields( const QgsFields &inputFields ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + + private: + + int mSegments = 72; +}; + +/** + * Native convex hull algorithm. + */ +class QgsConvexHullAlgorithm : public QgsProcessingFeatureBasedAlgorithm +{ + + public: + + QgsConvexHullAlgorithm() = default; + QString name() const override { return QStringLiteral( "convexhull" ); } + QString displayName() const override { return QObject::tr( "Convex hull" ); } + virtual QStringList tags() const override { return QObject::tr( "convex,hull,bounds,bounding" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry" ); } + QString shortHelpString() const override; + QgsConvexHullAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + QString outputName() const override { return QObject::tr( "Convex hulls" ); } + QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type ) const override { return QgsWkbTypes::Polygon; } + QgsFields outputFields( const QgsFields &inputFields ) const override; + QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override; + +}; + ///@endcond PRIVATE #endif // QGSNATIVEALGORITHMS_H From 95eab5127f0d1a79b7c1fc687364ec74fc12cc29 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 19:32:33 +1000 Subject: [PATCH 4/7] Remove redundant algorithms --- python/plugins/processing/algs/help/qgis.yaml | 13 -- .../processing/algs/qgis/BoundingBox.py | 75 ------- .../processing/algs/qgis/ConvexHull.py | 191 ------------------ .../algs/qgis/MinimalEnclosingCircle.py | 142 ------------- .../algs/qgis/OrientedMinimumBoundingBox.py | 148 -------------- .../algs/qgis/QGISAlgorithmProvider.py | 8 - .../tests/testdata/qgis_algorithm_tests.yaml | 38 ---- 7 files changed, 615 deletions(-) delete mode 100644 python/plugins/processing/algs/qgis/BoundingBox.py delete mode 100644 python/plugins/processing/algs/qgis/ConvexHull.py delete mode 100644 python/plugins/processing/algs/qgis/MinimalEnclosingCircle.py delete mode 100644 python/plugins/processing/algs/qgis/OrientedMinimumBoundingBox.py diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index a488f121bd7..e981811dfcb 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -43,9 +43,6 @@ qgis:basicstatisticsforfields: > qgis:boundary: > Returns the closure of the combinatorial boundary of the input geometries (ie the topological boundary of the geometry). For instance, a polygon geometry will have a boundary consisting of the linestrings for each ring in the polygon. Only valid for polygon or line layers. -qgis:boundingboxes: > - This algorithm calculates the bounding box (envelope) of each feature in an input layer. - qgis:buildvirtualvector: > This algorithm creates a virtual layer that contains a set of vector layer. @@ -76,11 +73,6 @@ qgis:convertgeometrytype: > See the "Polygonize" or "Lines to polygons" algorithm for alternative options. -qgis:convexhull: > - This algorithm computes the convex hull of features in a layer. - - If a field is specified, it will divide the features into classes based on that field, and compute a separate convex hull for the features in each class. - qgis:countpointsinpolygon: > This algorithm takes a points layer and a polygon layer and counts the number of points from the first one in each polygons of the second one. @@ -356,11 +348,6 @@ qgis:offsetline: > The miter limit parameter is only applicable for miter join styles, and controls the maximum distance from the offset curve to use when creating a mitered join. -qgis:orientedminimumboundingbox: > - This algorithm takes a vector layer and generate a new one with the minimum rectangle that covers all the input features. - - As an alternative, the output layer can contain not just a single rectangle, but one for each input feature, representing the minimum rectangle that covers each of them. - qgis:minimalenclosingcircle: > This algorithm takes a vector layer and generate a new one with the minimum enclosing circle that covers all the input features. diff --git a/python/plugins/processing/algs/qgis/BoundingBox.py b/python/plugins/processing/algs/qgis/BoundingBox.py deleted file mode 100644 index d00d7156b4e..00000000000 --- a/python/plugins/processing/algs/qgis/BoundingBox.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - BoundingBox.py - -------------- - Date : July 2016 - Copyright : (C) 2016 by Nyall Dawson - Email : nyall dot dawson at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Nyall Dawson' -__date__ = 'July 2016' -__copyright__ = '(C) 2016, Nyall Dawson' - -# This will get replaced with a git SHA1 when you do a git archive323 - -__revision__ = '$Format:%H$' - -import os - -from qgis.core import (QgsGeometry, - QgsWkbTypes, - QgsProcessingException) - - -from qgis.PyQt.QtGui import QIcon - -from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class BoundingBox(QgisFeatureBasedAlgorithm): - - def icon(self): - return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'matrix.png')) - - def group(self): - return self.tr('Vector geometry') - - def __init__(self): - super().__init__() - - def name(self): - return 'boundingboxes' - - def displayName(self): - return self.tr('Bounding boxes') - - def outputName(self): - return self.tr('Bounds') - - def outputWkbType(self, inputWkb): - return QgsWkbTypes.Polygon - - def processFeature(self, feature, feedback): - input_geometry = feature.geometry() - if input_geometry: - output_geometry = QgsGeometry.fromRect(input_geometry.boundingBox()) - if not output_geometry: - raise QgsProcessingException( - self.tr('Error calculating bounding box')) - - feature.setGeometry(output_geometry) - - return feature diff --git a/python/plugins/processing/algs/qgis/ConvexHull.py b/python/plugins/processing/algs/qgis/ConvexHull.py deleted file mode 100644 index 214140bab3a..00000000000 --- a/python/plugins/processing/algs/qgis/ConvexHull.py +++ /dev/null @@ -1,191 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - ConvexHull.py - --------------------- - Date : August 2012 - Copyright : (C) 2012 by Victor Olaya - Email : volayaf at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" -from builtins import str - -__author__ = 'Victor Olaya' -__date__ = 'August 2012' -__copyright__ = '(C) 2012, Victor Olaya' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -import os - -from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtCore import QVariant - -from qgis.core import (QgsField, - QgsFeature, - QgsFeatureSink, - QgsGeometry, - QgsWkbTypes, - QgsFeatureRequest, - QgsFields, - NULL, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSink, - QgsProcessing, - QgsProcessingException) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.tools import dataobjects, vector - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class ConvexHull(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' - FIELD = 'FIELD' - - def icon(self): - return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'convex_hull.png')) - - def group(self): - return self.tr('Vector geometry') - - def __init__(self): - super().__init__() - - 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 creating convex hulls by classes)'), - parentLayerParameterName=self.INPUT, optional=True)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Convex hull'), QgsProcessing.TypeVectorPolygon)) - - def name(self): - return 'convexhull' - - def displayName(self): - return self.tr('Convex hull') - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fieldName = self.parameterAsString(parameters, self.FIELD, context) - useField = bool(fieldName) - - field_index = None - f = QgsField('value', QVariant.String, '', 255) - if useField: - field_index = source.fields().lookupField(fieldName) - fType = source.fields()[field_index].type() - if fType in [QVariant.Int, QVariant.UInt, QVariant.LongLong, QVariant.ULongLong]: - f.setType(fType) - f.setLength(20) - elif fType == QVariant.Double: - f.setType(QVariant.Double) - f.setLength(20) - f.setPrecision(6) - else: - f.setType(QVariant.String) - f.setLength(255) - - fields = QgsFields() - fields.append(QgsField('id', QVariant.Int, '', 20)) - fields.append(f) - fields.append(QgsField('area', QVariant.Double, '', 20, 6)) - fields.append(QgsField('perim', QVariant.Double, '', 20, 6)) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, source.sourceCrs()) - - outFeat = QgsFeature() - outGeom = QgsGeometry() - - fid = 0 - val = None - if useField: - unique = source.uniqueValues(field_index) - current = 0 - total = 100.0 / (source.featureCount() * len(unique)) if source.featureCount() else 1 - for i in unique: - if feedback.isCanceled(): - break - - first = True - hull = [] - features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_index])) - for f in features: - if feedback.isCanceled(): - break - - idVar = f.attributes()[field_index] - if str(idVar).strip() == str(i).strip(): - if first: - val = idVar - first = False - - inGeom = f.geometry() - points = vector.extractPoints(inGeom) - hull.extend(points) - current += 1 - feedback.setProgress(int(current * total)) - - if len(hull) >= 3: - tmpGeom = QgsGeometry(outGeom.fromMultiPoint(hull)) - try: - outGeom = tmpGeom.convexHull() - if outGeom: - area = outGeom.geometry().area() - perim = outGeom.geometry().perimeter() - else: - area = NULL - perim = NULL - outFeat.setGeometry(outGeom) - outFeat.setAttributes([fid, val, area, perim]) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - except: - raise QgsProcessingException( - self.tr('Exception while computing convex hull')) - fid += 1 - else: - hull = [] - total = 100.0 / source.featureCount() if source.featureCount() else 1 - features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([])) - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - inGeom = f.geometry() - points = vector.extractPoints(inGeom) - hull.extend(points) - feedback.setProgress(int(current * total)) - - tmpGeom = QgsGeometry(outGeom.fromMultiPoint(hull)) - try: - outGeom = tmpGeom.convexHull() - if outGeom: - area = outGeom.geometry().area() - perim = outGeom.geometry().perimeter() - else: - area = NULL - perim = NULL - outFeat.setGeometry(outGeom) - outFeat.setAttributes([0, 'all', area, perim]) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - except: - raise QgsProcessingException( - self.tr('Exception while computing convex hull')) - - return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/MinimalEnclosingCircle.py b/python/plugins/processing/algs/qgis/MinimalEnclosingCircle.py deleted file mode 100644 index a31e8b48526..00000000000 --- a/python/plugins/processing/algs/qgis/MinimalEnclosingCircle.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - MinimalEnclosingCircle.py - --------------------- - Date : September 2017 - Copyright : (C) 2017, Loïc BARTOLETTI - Email : lbartoletti at tuxfamily dot org -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Loïc BARTOLETTI' -__date__ = 'September 2017' -__copyright__ = '(C) 2017, Loïc BARTOLETTI' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsField, - QgsFields, - QgsFeatureSink, - QgsGeometry, - QgsFeature, - QgsWkbTypes, - QgsFeatureRequest, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterBoolean, - QgsProcessingParameterFeatureSink, - QgsProcessingException) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm - - -class MinimalEnclosingCircle(QgisAlgorithm): - - INPUT = 'INPUT' - BY_FEATURE = 'BY_FEATURE' - - OUTPUT = 'OUTPUT' - - def group(self): - return self.tr('Vector general') - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), [QgsProcessing.TypeVectorAnyGeometry])) - self.addParameter(QgsProcessingParameterBoolean(self.BY_FEATURE, - self.tr('Calculate bounds for each feature separately'), defaultValue=True)) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Enclosing circles'), QgsProcessing.TypeVectorPolygon)) - - def name(self): - return 'minimalenclosingcircle' - - def displayName(self): - return self.tr('Minimal enclosing circle') - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - by_feature = self.parameterAsBool(parameters, self.BY_FEATURE, context) - - if not by_feature and QgsWkbTypes.geometryType(source.wkbType()) == QgsWkbTypes.PointGeometry and source.featureCount() <= 2: - raise QgsProcessingException(self.tr("Can't calculate a minimal enclosing circle for each point, it's a point. The number of points must be greater than 2")) - - if by_feature: - fields = source.fields() - else: - fields = QgsFields() - fields.append(QgsField('center_x', QVariant.Double)) - fields.append(QgsField('center_y', QVariant.Double)) - fields.append(QgsField('radius', QVariant.Double)) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, source.sourceCrs()) - - if by_feature: - self.featureMec(source, context, sink, feedback) - else: - self.layerMec(source, context, sink, feedback) - - return {self.OUTPUT: dest_id} - - def layerMec(self, source, context, sink, feedback): - req = QgsFeatureRequest().setSubsetOfAttributes([]) - features = source.getFeatures(req) - total = 100.0 / source.featureCount() if source.featureCount() else 0 - newgeometry = QgsGeometry() - first = True - geometries = [] - for current, inFeat in enumerate(features): - if feedback.isCanceled(): - break - - if inFeat.hasGeometry(): - geometries.append(inFeat.geometry()) - feedback.setProgress(int(current * total)) - - newgeometry = QgsGeometry.unaryUnion(geometries) - geometry, center, radius = newgeometry.minimalEnclosingCircle() - - if geometry: - outFeat = QgsFeature() - - outFeat.setGeometry(geometry) - outFeat.setAttributes([center.x(), - center.y(), - radius]) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - - def featureMec(self, source, context, sink, feedback): - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - outFeat = QgsFeature() - for current, inFeat in enumerate(features): - if feedback.isCanceled(): - break - - geometry, center, radius = inFeat.geometry().minimalEnclosingCircle() - if geometry: - outFeat.setGeometry(geometry) - attrs = inFeat.attributes() - attrs.extend([center.x(), - center.y(), - radius]) - outFeat.setAttributes(attrs) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - else: - feedback.pushInfo(self.tr("Can't calculate a minimal enclosing circle for feature {0}.").format(inFeat.id())) - feedback.setProgress(int(current * total)) diff --git a/python/plugins/processing/algs/qgis/OrientedMinimumBoundingBox.py b/python/plugins/processing/algs/qgis/OrientedMinimumBoundingBox.py deleted file mode 100644 index 1d85ee9eb2e..00000000000 --- a/python/plugins/processing/algs/qgis/OrientedMinimumBoundingBox.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - OrientedMinimumBoundingBox.py - --------------------- - Date : June 2015 - Copyright : (C) 2015, Loïc BARTOLETTI - Email : coder at tuxfamily dot org -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Loïc BARTOLETTI' -__date__ = 'June 2015' -__copyright__ = '(C) 2015, Loïc BARTOLETTI' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsField, - QgsFields, - QgsFeatureSink, - QgsGeometry, - QgsFeature, - QgsWkbTypes, - QgsFeatureRequest, - QgsProcessing, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterBoolean, - QgsProcessingParameterFeatureSink, - QgsProcessingException) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm - - -class OrientedMinimumBoundingBox(QgisAlgorithm): - - INPUT = 'INPUT' - BY_FEATURE = 'BY_FEATURE' - - OUTPUT = 'OUTPUT' - - def group(self): - return self.tr('Vector general') - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'), [QgsProcessing.TypeVectorAnyGeometry])) - self.addParameter(QgsProcessingParameterBoolean(self.BY_FEATURE, - self.tr('Calculate bounds for each feature separately'), defaultValue=True)) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Bounding boxes'), QgsProcessing.TypeVectorPolygon)) - - def name(self): - return 'orientedminimumboundingbox' - - def displayName(self): - return self.tr('Oriented minimum bounding box') - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - by_feature = self.parameterAsBool(parameters, self.BY_FEATURE, context) - - if not by_feature and QgsWkbTypes.geometryType(source.wkbType()) == QgsWkbTypes.PointGeometry and source.featureCount() <= 2: - raise QgsProcessingException(self.tr("Can't calculate an OMBB for each point, it's a point. The number of points must be greater than 2")) - - if by_feature: - fields = source.fields() - else: - fields = QgsFields() - fields.append(QgsField('area', QVariant.Double)) - fields.append(QgsField('perimeter', QVariant.Double)) - fields.append(QgsField('angle', QVariant.Double)) - fields.append(QgsField('width', QVariant.Double)) - fields.append(QgsField('height', QVariant.Double)) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, source.sourceCrs()) - - if by_feature: - self.featureOmbb(source, context, sink, feedback) - else: - self.layerOmmb(source, context, sink, feedback) - - return {self.OUTPUT: dest_id} - - def layerOmmb(self, source, context, sink, feedback): - req = QgsFeatureRequest().setSubsetOfAttributes([]) - features = source.getFeatures(req) - total = 100.0 / source.featureCount() if source.featureCount() else 0 - newgeometry = QgsGeometry() - first = True - geometries = [] - for current, inFeat in enumerate(features): - if feedback.isCanceled(): - break - - if inFeat.hasGeometry(): - geometries.append(inFeat.geometry()) - feedback.setProgress(int(current * total)) - - newgeometry = QgsGeometry.unaryUnion(geometries) - geometry, area, angle, width, height = newgeometry.orientedMinimumBoundingBox() - - if geometry: - outFeat = QgsFeature() - - outFeat.setGeometry(geometry) - outFeat.setAttributes([area, - width * 2 + height * 2, - angle, - width, - height]) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - - def featureOmbb(self, source, context, sink, feedback): - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - outFeat = QgsFeature() - for current, inFeat in enumerate(features): - if feedback.isCanceled(): - break - - geometry, area, angle, width, height = inFeat.geometry().orientedMinimumBoundingBox() - if geometry: - outFeat.setGeometry(geometry) - attrs = inFeat.attributes() - attrs.extend([area, - width * 2 + height * 2, - angle, - width, - height]) - outFeat.setAttributes(attrs) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - else: - feedback.pushInfo(self.tr("Can't calculate an OMBB for feature {0}.").format(inFeat.id())) - feedback.setProgress(int(current * total)) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 7299b188c5c..6b858b696f7 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -46,10 +46,8 @@ from .Aspect import Aspect from .AutoincrementalField import AutoincrementalField from .BasicStatistics import BasicStatisticsForField from .Boundary import Boundary -from .BoundingBox import BoundingBox from .CheckValidity import CheckValidity from .ConcaveHull import ConcaveHull -from .ConvexHull import ConvexHull from .CreateAttributeIndex import CreateAttributeIndex from .CreateConstantRaster import CreateConstantRaster from .Datasources2Vrt import Datasources2Vrt @@ -101,10 +99,8 @@ from .MeanCoords import MeanCoords from .Merge import Merge from .MergeLines import MergeLines from .MinimumBoundingGeometry import MinimumBoundingGeometry -from .MinimalEnclosingCircle import MinimalEnclosingCircle from .NearestNeighbourAnalysis import NearestNeighbourAnalysis from .OffsetLine import OffsetLine -from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox from .Orthogonalize import Orthogonalize from .PointDistance import PointDistance from .PointOnSurface import PointOnSurface @@ -201,10 +197,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider): AutoincrementalField(), BasicStatisticsForField(), Boundary(), - BoundingBox(), CheckValidity(), ConcaveHull(), - ConvexHull(), CreateAttributeIndex(), CreateConstantRaster(), Datasources2Vrt(), @@ -256,10 +250,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider): Merge(), MergeLines(), MinimumBoundingGeometry(), - MinimalEnclosingCircle(), NearestNeighbourAnalysis(), OffsetLine(), - OrientedMinimumBoundingBox(), Orthogonalize(), PointDistance(), PointOnSurface(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index db4f901cb4e..2cb45535ff2 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2510,29 +2510,6 @@ tests: name: expected/join_attribute_table.gml type: vector - - algorithm: qgis:convexhull - name: Simple convex hull - params: - INPUT: - name: custom/points_hull.gml - type: vector - results: - OUTPUT: - name: expected/convex_hull.gml - type: vector - - - algorithm: qgis:convexhull - name: Convex hull based on field attribute - params: - FIELD: hull - INPUT: - name: custom/points_hull.gml - type: vector - results: - OUTPUT: - name: expected/convex_hull_fields.gml - type: vector - # # These tests dissabled because algs require access to iface which # # is not available in the test suite. # #- algorithm: qgis:shortestpathpointtopoint @@ -3254,21 +3231,6 @@ tests: geometry: precision: 7 - - algorithm: qgis:minimalenclosingcircle - name: Minimal enclosing circle all features - params: - BY_FEATURE: false - INPUT: - name: custom/oriented_bbox.gml - type: vector - results: - OUTPUT: - name: expected/enclosing_circles_all.gml - type: vector - compare: - geometry: - precision: 7 - - algorithm: qgis:minimumboundinggeometry name: Minimum enclosing geom (hull, no field) params: From 9eeecbed659f5416d12ee6f7243b0fa8cb3f1c48 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 19:35:41 +1000 Subject: [PATCH 5/7] Add note to see 'minimum bounding geometry' alg to feature by feature alg help --- src/core/processing/qgsnativealgorithms.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 8a2f17df3ae..21ec2c908ba 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -1093,7 +1093,7 @@ QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap QString QgsBoundingBoxAlgorithm::shortHelpString() const { - return QObject::tr( "This algorithm calculates the bounding box (envelope) of each feature in an input layer." ); + return QObject::tr( "This algorithm calculates the bounding box (envelope) for each feature in an input layer.\n\nSee the 'Minimum bounding geometry' algorithm for a bounding box calculation which covers the whole layer or grouped subsets of features." ); } QgsBoundingBoxAlgorithm *QgsBoundingBoxAlgorithm::createInstance() const @@ -1131,7 +1131,7 @@ QgsFeature QgsBoundingBoxAlgorithm::processFeature( const QgsFeature &feature, Q QString QgsOrientedMinimumBoundingBoxAlgorithm::shortHelpString() const { - return QObject::tr( "This algorithm calculates the minimum area rotated rectangle which covers each feature in an input layer." ); + return QObject::tr( "This algorithm calculates the minimum area rotated rectangle which covers each feature in an input layer.\n\nSee the 'Minimum bounding geometry' algorithm for a oriented bounding box calculation which covers the whole layer or grouped subsets of features." ); } QgsOrientedMinimumBoundingBoxAlgorithm *QgsOrientedMinimumBoundingBoxAlgorithm::createInstance() const @@ -1181,7 +1181,7 @@ void QgsMinimumEnclosingCircleAlgorithm::initParameters( const QVariantMap & ) QString QgsMinimumEnclosingCircleAlgorithm::shortHelpString() const { - return QObject::tr( "This algorithm calculates the minimum enclosing circle which covers each feature in an input layer." ); + return QObject::tr( "This algorithm calculates the minimum enclosing circle which covers each feature in an input layer.\n\nSee the 'Minimum bounding geometry' algorithm for a minimal enclosing circle calculation which covers the whole layer or grouped subsets of features." ); } QgsMinimumEnclosingCircleAlgorithm *QgsMinimumEnclosingCircleAlgorithm::createInstance() const @@ -1222,7 +1222,7 @@ QgsFeature QgsMinimumEnclosingCircleAlgorithm::processFeature( const QgsFeature QString QgsConvexHullAlgorithm::shortHelpString() const { - return QObject::tr( "This algorithm calculates the convex hull for each feature in an input layer." ); + return QObject::tr( "This algorithm calculates the convex hull for each feature in an input layer.\n\nSee the 'Minimum bounding geometry' algorithm for a convex hull calculation which covers the whole layer or grouped subsets of features." ); } QgsConvexHullAlgorithm *QgsConvexHullAlgorithm::createInstance() const From 5d504dcafb5ff8e76e4ca8f7ea99f0a4fcb532ad Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 3 Sep 2017 19:42:49 +1000 Subject: [PATCH 6/7] Unify processing "polygon from layer extent" algorithms Now that the extra features of the "polygon from vector layer extent" algorithm are covered by the new "Minimum bounding geometry" algorithm, we can replace the previous two "polygon from vector extent" and "polygon from raster extent" algorithms by a single "polygon from layer extent" algorithm. --- python/plugins/processing/algs/help/qgis.yaml | 7 +- .../processing/algs/qgis/ExtentFromLayer.py | 73 ++-------- .../algs/qgis/ExtentFromRasterLayer.py | 127 ------------------ .../algs/qgis/QGISAlgorithmProvider.py | 2 - .../tests/testdata/qgis_algorithm_tests.yaml | 2 +- 5 files changed, 15 insertions(+), 196 deletions(-) delete mode 100755 python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index e981811dfcb..170ec485b03 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -402,12 +402,7 @@ qgis:polygoncentroids: > NOTE: This algorithm is deprecated and the generic "centroids" algorithm (which works for line and multi geometry layers) should be used instead. qgis:polygonfromlayerextent: > - This algorithm takes a vector layer and generates a new one with the minimum bounding box (rectangle with N-S orientation) that covers all the input features. - - As an alternative, the output layer can contain not just a single bounding box, but one for each input feature, representing the bounding box of each of them. - -qgis:polygonfromrasterextent: > - This algorithm takes a raster layer and generates a vector layer containing a feature with the minimum bounding box that covers the raster layer's extent. + This algorithm takes a map layer and generates a new vector layer with the minimum bounding box (rectangle with N-S orientation) that covers the input layer. qgis:polygonize: > This algorithm takes a lines layer and creates a polygon layer, with polygons generated from the lines in the input layer. diff --git a/python/plugins/processing/algs/qgis/ExtentFromLayer.py b/python/plugins/processing/algs/qgis/ExtentFromLayer.py index 61a21426d53..99aa9232a83 100644 --- a/python/plugins/processing/algs/qgis/ExtentFromLayer.py +++ b/python/plugins/processing/algs/qgis/ExtentFromLayer.py @@ -32,14 +32,12 @@ from qgis.PyQt.QtCore import QVariant from qgis.core import (QgsField, QgsFeatureSink, - QgsPointXY, QgsGeometry, QgsFeature, QgsWkbTypes, QgsProcessing, - QgsProcessingParameterFeatureSource, + QgsProcessingParameterMapLayer, QgsProcessingParameterFeatureSink, - QgsProcessingParameterBoolean, QgsFields) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -61,27 +59,23 @@ class ExtentFromLayer(QgisAlgorithm): return self.tr('extent,envelope,bounds,bounding,boundary,layer').split(',') def group(self): - return self.tr('Vector general') + return self.tr('Layer tools') def __init__(self): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterBoolean(self.BY_FEATURE, - self.tr('Calculate extent for each feature separately'), False)) - + self.addParameter(QgsProcessingParameterMapLayer(self.INPUT, self.tr('Input layer'))) self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extent'), type=QgsProcessing.TypeVectorPolygon)) def name(self): return 'polygonfromlayerextent' def displayName(self): - return self.tr('Polygon from vector extent') + return self.tr('Polygon from layer extent') def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - byFeature = self.parameterAsBool(parameters, self.BY_FEATURE, context) + layer = self.parameterAsLayer(parameters, self.INPUT, context) fields = QgsFields() fields.append(QgsField('MINX', QVariant.Double)) @@ -96,17 +90,15 @@ class ExtentFromLayer(QgisAlgorithm): fields.append(QgsField('WIDTH', QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, source.sourceCrs()) + fields, QgsWkbTypes.Polygon, layer.crs()) - if byFeature: - self.featureExtent(source, context, sink, feedback) - else: - self.layerExtent(source, sink, feedback) + try: + # may not be possible + layer.updateExtents() + except: + pass - return {self.OUTPUT: dest_id} - - def layerExtent(self, source, sink, feedback): - rect = source.sourceExtent() + rect = layer.extent() geometry = QgsGeometry.fromRect(rect) minx = rect.xMinimum() miny = rect.yMinimum() @@ -136,43 +128,4 @@ class ExtentFromLayer(QgisAlgorithm): feat.setAttributes(attrs) sink.addFeature(feat, QgsFeatureSink.FastInsert) - def featureExtent(self, source, context, sink, feedback): - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - feat = QgsFeature() - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - rect = f.geometry().boundingBox() - minx = rect.xMinimum() - miny = rect.yMinimum() - maxx = rect.xMaximum() - maxy = rect.yMaximum() - height = rect.height() - width = rect.width() - cntx = minx + width / 2.0 - cnty = miny + height / 2.0 - area = width * height - perim = 2 * width + 2 * height - rect = [QgsPointXY(minx, miny), QgsPointXY(minx, maxy), QgsPointXY(maxx, - maxy), QgsPointXY(maxx, miny), QgsPointXY(minx, miny)] - - geometry = QgsGeometry().fromPolygon([rect]) - feat.setGeometry(geometry) - attrs = [ - minx, - miny, - maxx, - maxy, - cntx, - cnty, - area, - perim, - height, - width, - ] - feat.setAttributes(attrs) - - sink.addFeature(feat, QgsFeatureSink.FastInsert) - feedback.setProgress(int(current * total)) + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py b/python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py deleted file mode 100755 index 63efd366aef..00000000000 --- a/python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - ExtentFromRasterLayer.py - --------------------- - Date : August 2017 - Copyright : (C) 2017 by Nyall Dawson - Email : nyall dot dawson at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Nyall Dawson' -__date__ = 'August 2017' -__copyright__ = '(C) 2017, Nyall Dawson' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -import os - -from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtCore import QVariant - -from qgis.core import (QgsField, - QgsFeatureSink, - QgsPointXY, - QgsGeometry, - QgsFeature, - QgsWkbTypes, - QgsProcessing, - QgsProcessingParameterRasterLayer, - QgsProcessingParameterFeatureSink, - QgsFields) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class ExtentFromRasterLayer(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' - - def icon(self): - return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'layer_extent.png')) - - def tags(self): - return self.tr('extent,envelope,bounds,bounding,boundary,layer').split(',') - - def group(self): - return self.tr('Raster tools') - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT, self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extent'), type=QgsProcessing.TypeVectorPolygon)) - - def name(self): - return 'polygonfromrasterextent' - - def displayName(self): - return self.tr('Polygon from raster extent') - - def processAlgorithm(self, parameters, context, feedback): - raster = self.parameterAsRasterLayer(parameters, self.INPUT, context) - - fields = QgsFields() - fields.append(QgsField('MINX', QVariant.Double)) - fields.append(QgsField('MINY', QVariant.Double)) - fields.append(QgsField('MAXX', QVariant.Double)) - fields.append(QgsField('MAXY', QVariant.Double)) - fields.append(QgsField('CNTX', QVariant.Double)) - fields.append(QgsField('CNTY', QVariant.Double)) - fields.append(QgsField('AREA', QVariant.Double)) - fields.append(QgsField('PERIM', QVariant.Double)) - fields.append(QgsField('HEIGHT', QVariant.Double)) - fields.append(QgsField('WIDTH', QVariant.Double)) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, raster.crs()) - - self.layerExtent(raster, sink, feedback) - - return {self.OUTPUT: dest_id} - - def layerExtent(self, raster, sink, feedback): - rect = raster.extent() - geometry = QgsGeometry.fromRect(rect) - minx = rect.xMinimum() - miny = rect.yMinimum() - maxx = rect.xMaximum() - maxy = rect.yMaximum() - height = rect.height() - width = rect.width() - cntx = minx + width / 2.0 - cnty = miny + height / 2.0 - area = width * height - perim = 2 * width + 2 * height - - feat = QgsFeature() - feat.setGeometry(geometry) - attrs = [ - minx, - miny, - maxx, - maxy, - cntx, - cnty, - area, - perim, - height, - width, - ] - feat.setAttributes(attrs) - sink.addFeature(feat, QgsFeatureSink.FastInsert) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 6b858b696f7..c96471346f2 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -68,7 +68,6 @@ from .Explode import Explode from .ExportGeometryInfo import ExportGeometryInfo from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer -from .ExtentFromRasterLayer import ExtentFromRasterLayer from .ExtractNodes import ExtractNodes from .ExtractSpecificNodes import ExtractSpecificNodes from .FieldPyculator import FieldsPyculator @@ -219,7 +218,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): ExportGeometryInfo(), ExtendLines(), ExtentFromLayer(), - ExtentFromRasterLayer(), ExtractNodes(), ExtractSpecificNodes(), FieldsCalculator(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 2cb45535ff2..fc0c5b4c551 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -3205,7 +3205,7 @@ tests: name: expected/execute_sql.gml type: vector - - algorithm: qgis:polygonfromrasterextent + - algorithm: qgis:polygonfromlayerextent name: Polygon from raster extent params: INPUT: From f01ad63bbad41b0196323668e1f246ab9aca73e9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 4 Sep 2017 08:45:07 +1000 Subject: [PATCH 7/7] Rename 'Polygon from layer extent' to 'Extract layer extent' It's slightly simpler to understand and better reflects what this alg does --- python/plugins/processing/algs/qgis/ExtentFromLayer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ExtentFromLayer.py b/python/plugins/processing/algs/qgis/ExtentFromLayer.py index 99aa9232a83..bf213fa586f 100644 --- a/python/plugins/processing/algs/qgis/ExtentFromLayer.py +++ b/python/plugins/processing/algs/qgis/ExtentFromLayer.py @@ -56,7 +56,7 @@ class ExtentFromLayer(QgisAlgorithm): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'layer_extent.png')) def tags(self): - return self.tr('extent,envelope,bounds,bounding,boundary,layer').split(',') + return self.tr('polygon,from,vector,raster,extent,envelope,bounds,bounding,boundary,layer').split(',') def group(self): return self.tr('Layer tools') @@ -72,7 +72,7 @@ class ExtentFromLayer(QgisAlgorithm): return 'polygonfromlayerextent' def displayName(self): - return self.tr('Polygon from layer extent') + return self.tr('Extract layer extent') def processAlgorithm(self, parameters, context, feedback): layer = self.parameterAsLayer(parameters, self.INPUT, context)