diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 13c8704e439..6615f66f4c7 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -99,6 +99,8 @@ from .RandomPointsAlongLines import RandomPointsAlongLines from .RandomPointsExtent import RandomPointsExtent from .RandomPointsLayer import RandomPointsLayer from .RandomPointsPolygons import RandomPointsPolygons +from .RandomSelection import RandomSelection +from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets from .RasterLayerStatistics import RasterLayerStatistics from .RegularPoints import RegularPoints from .ReverseLineDirection import ReverseLineDirection @@ -135,8 +137,6 @@ from .VoronoiPolygons import VoronoiPolygons from .ZonalStatistics import ZonalStatistics # from .ExtractByLocation import ExtractByLocation -# from .RandomSelection import RandomSelection -# from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets # from .SelectByLocation import SelectByLocation # from .SpatialJoin import SpatialJoin # from .GridLine import GridLine @@ -185,7 +185,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider): def getAlgs(self): # algs = [ - # RandomSelection(), RandomSelectionWithinSubsets(), # SelectByLocation(), # ExtractByLocation(), # SpatialJoin(), @@ -270,6 +269,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider): RandomPointsExtent(), RandomPointsLayer(), RandomPointsPolygons(), + RandomSelection(), + RandomSelectionWithinSubsets(), RasterLayerStatistics(), RegularPoints(), ReverseLineDirection(), diff --git a/python/plugins/processing/algs/qgis/RandomSelection.py b/python/plugins/processing/algs/qgis/RandomSelection.py index 167c3c6729f..31af20a29f8 100644 --- a/python/plugins/processing/algs/qgis/RandomSelection.py +++ b/python/plugins/processing/algs/qgis/RandomSelection.py @@ -30,13 +30,16 @@ import os import random from qgis.PyQt.QtGui import QIcon -from qgis.core import QgsFeatureSink, QgsProcessingUtils +from qgis.core import (QgsFeatureSink, + QgsProcessingException, + QgsProcessingUtils, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterEnum, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer) + from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterSelection -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputVector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -61,13 +64,14 @@ class RandomSelection(QgisAlgorithm): self.methods = [self.tr('Number of selected features'), self.tr('Percentage of selected features')] - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'))) - self.addParameter(ParameterSelection(self.METHOD, - self.tr('Method'), self.methods, 0)) - self.addParameter(ParameterNumber(self.NUMBER, - self.tr('Number/percentage of selected features'), 0, None, 10)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Selection'), True)) + self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, + self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterEnum(self.METHOD, + self.tr('Method'), self.methods, False, 0)) + self.addParameter(QgsProcessingParameterNumber(self.NUMBER, + self.tr('Number/percentage of selected features'), QgsProcessingParameterNumber.Integer, + 10, False, 0.0, 999999999999.0)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (random)'))) def name(self): return 'randomselection' @@ -76,23 +80,20 @@ class RandomSelection(QgisAlgorithm): return self.tr('Random selection') def processAlgorithm(self, parameters, context, feedback): - filename = self.getParameterValue(self.INPUT) - layer = QgsProcessingUtils.mapLayerFromString(filename, context) - method = self.getParameterValue(self.METHOD) + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + method = self.parameterAsEnum(parameters, self.METHOD, context) featureCount = layer.featureCount() - value = int(self.getParameterValue(self.NUMBER)) - - layer.removeSelection() + value = self.parameterAsInt(parameters, self.NUMBER, context) if method == 0: if value > featureCount: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Selected number is greater than feature count. ' 'Choose a lower value and try again.')) else: if value > 100: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr("Percentage can't be greater than 100. Set a " "different value and try again.")) value = int(round(value / 100.0, 4) * featureCount) @@ -100,4 +101,4 @@ class RandomSelection(QgisAlgorithm): selran = random.sample(list(range(featureCount)), value) layer.selectByIds(selran) - self.setOutputValue(self.OUTPUT, filename) + return {self.OUTPUT: parameters[self.INPUT]} diff --git a/python/plugins/processing/algs/qgis/RandomSelectionWithinSubsets.py b/python/plugins/processing/algs/qgis/RandomSelectionWithinSubsets.py index 6086c31efcf..c72390a4976 100644 --- a/python/plugins/processing/algs/qgis/RandomSelectionWithinSubsets.py +++ b/python/plugins/processing/algs/qgis/RandomSelectionWithinSubsets.py @@ -31,15 +31,17 @@ import random from qgis.PyQt.QtGui import QIcon -from qgis.core import QgsFeature, QgsFeatureSink, QgsProcessingUtils - +from qgis.core import (QgsFeatureRequest, + QgsProcessingException, + QgsProcessingUtils, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterEnum, + QgsProcessingParameterField, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer) +from collections import defaultdict from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterSelection -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.parameters import ParameterTableField -from processing.core.outputs import OutputVector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -65,16 +67,17 @@ class RandomSelectionWithinSubsets(QgisAlgorithm): self.methods = [self.tr('Number of selected features'), self.tr('Percentage of selected features')] - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'))) - self.addParameter(ParameterTableField(self.FIELD, - self.tr('ID Field'), self.INPUT)) - self.addParameter(ParameterSelection(self.METHOD, - self.tr('Method'), self.methods, 0)) - self.addParameter(ParameterNumber(self.NUMBER, - self.tr('Number/percentage of selected features'), 1, None, 10)) - - self.addOutput(OutputVector(self.OUTPUT, self.tr('Selection stratified'), True)) + self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, + self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterField(self.FIELD, + self.tr('ID field'), None, self.INPUT)) + self.addParameter(QgsProcessingParameterEnum(self.METHOD, + self.tr('Method'), self.methods, False, 0)) + self.addParameter(QgsProcessingParameterNumber(self.NUMBER, + self.tr('Number/percentage of selected features'), + QgsProcessingParameterNumber.Integer, + 10, False, 0.0, 999999999999.0)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (stratified random)'))) def name(self): return 'randomselectionwithinsubsets' @@ -83,61 +86,52 @@ class RandomSelectionWithinSubsets(QgisAlgorithm): return self.tr('Random selection within subsets') def processAlgorithm(self, parameters, context, feedback): - filename = self.getParameterValue(self.INPUT) + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + method = self.parameterAsEnum(parameters, self.METHOD, context) + field = self.parameterAsString(parameters, self.FIELD, context) - layer = QgsProcessingUtils.mapLayerFromString(filename, context) - field = self.getParameterValue(self.FIELD) - method = self.getParameterValue(self.METHOD) - - layer.removeSelection() index = layer.fields().lookupField(field) - unique = QgsProcessingUtils.uniqueValues(layer, index, context) + unique = layer.uniqueValues(index) featureCount = layer.featureCount() - value = int(self.getParameterValue(self.NUMBER)) + value = self.parameterAsInt(parameters, self.NUMBER, context) if method == 0: if value > featureCount: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Selected number is greater that feature count. ' 'Choose lesser value and try again.')) else: if value > 100: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr("Percentage can't be greater than 100. Set a " "different value and try again.")) value = value / 100.0 - selran = [] - inFeat = QgsFeature() - - current = 0 total = 100.0 / (featureCount * len(unique)) if featureCount else 1 if not len(unique) == featureCount: - for i in unique: - features = QgsProcessingUtils.getFeatures(layer, context) - FIDs = [] - for inFeat in features: - attrs = inFeat.attributes() - if attrs[index] == i: - FIDs.append(inFeat.id()) - current += 1 - feedback.setProgress(int(current * total)) + classes = defaultdict(list) - if method == 1: - selValue = int(round(value * len(FIDs), 0)) - else: - selValue = value + features = layer.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([index])) - if selValue >= len(FIDs): - selFeat = FIDs - else: - selFeat = random.sample(FIDs, selValue) + for i, feature in enumerate(features): + if feedback.isCanceled(): + break + + classes[feature.attributes()[index]].append(feature.id()) + feedback.setProgress(int(i * total)) + + selran = [] + for subset in classes.values(): + if feedback.isCanceled(): + break + + selValue = value if method != 1 else int(round(value * len(subset), 0)) + selran.extend(random.sample(subset, selValue)) - selran.extend(selFeat) layer.selectByIds(selran) else: layer.selectByIds(list(range(featureCount))) # FIXME: implies continuous feature ids - self.setOutputValue(self.OUTPUT, filename) + return {self.OUTPUT: parameters[self.INPUT]}