def runGetFeatureTests(self, source): FeatureSourceTestCase.runGetFeatureTests(self, source) # combination of an uncompilable expression and limit feature = next(self.vl.getFeatures('pk=4')) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('parent', feature) context.appendScope(scope) request = QgsFeatureRequest() request.setExpressionContext(context) request.setFilterExpression('"pk" = attribute(@parent, \'pk\')') request.setLimit(1) values = [f['pk'] for f in self.vl.getFeatures(request)] self.assertEqual(values, [4])
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) expression_string = self.parameterAsExpression(parameters, self.EXPRESSION, context) (matching_sink, matching_sink_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) (nonmatching_sink, non_matching_sink_id) = self.parameterAsSink(parameters, self.FAIL_OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) expression = QgsExpression(expression_string) if expression.hasParserError(): raise GeoAlgorithmExecutionException(expression.parserErrorString()) expression_context = self.createExpressionContext(parameters, context) if not nonmatching_sink: # not saving failing features - so only fetch good features req = QgsFeatureRequest().setFilterExpression(expression_string) req.setExpressionContext(expression_context) for f in source.getFeatures(req): if feedback.isCanceled(): break matching_sink.addFeature(f) else: # saving non-matching features, so we need EVERYTHING expression_context.setFields(source.fields()) expression.prepare(expression_context) total = 100.0 / source.featureCount() for current, f in enumerate(source.getFeatures()): if feedback.isCanceled(): break expression_context.setFeature(f) if expression.evaluate(expression_context): matching_sink.addFeature(f) else: nonmatching_sink.addFeature(f) feedback.setProgress(int(current * total)) results = {self.OUTPUT: matching_sink_id} if nonmatching_sink: results[self.FAIL_OUTPUT] = non_matching_sink_id return results
def testAssignment(self): req = QgsFeatureRequest().setFilterFids([8, 9]).setFilterRect( QgsRectangle(1, 2, 3, 4)).setInvalidGeometryCheck( QgsFeatureRequest.GeometrySkipInvalid).setLimit(6).setFlags( QgsFeatureRequest.ExactIntersect).setSubsetOfAttributes( [1, 4]).setTimeout(6).setRequestMayBeNested(True) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('a', 6) context.appendScope(scope) req.setExpressionContext(context) method = QgsSimplifyMethod() method.setMethodType(QgsSimplifyMethod.PreserveTopology) req.setSimplifyMethod(method) context = QgsCoordinateTransformContext() req.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'), context) req2 = QgsFeatureRequest(req) self.assertEqual(req2.limit(), 6) self.assertCountEqual(req2.filterFids(), [8, 9]) self.assertEqual(req2.filterRect(), QgsRectangle(1, 2, 3, 4)) self.assertEqual(req2.invalidGeometryCheck(), QgsFeatureRequest.GeometrySkipInvalid) self.assertEqual(req2.expressionContext().scopeCount(), 1) self.assertEqual(req2.expressionContext().variable('a'), 6) self.assertEqual( req2.flags(), QgsFeatureRequest.ExactIntersect | QgsFeatureRequest.SubsetOfAttributes) self.assertEqual(req2.subsetOfAttributes(), [1, 4]) self.assertEqual(req2.simplifyMethod().methodType(), QgsSimplifyMethod.PreserveTopology) self.assertEqual(req2.destinationCrs().authid(), 'EPSG:3857') self.assertEqual(req2.timeout(), 6) self.assertTrue(req2.requestMayBeNested())
def fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() # add a new scope corresponding to the source layer -- this will potentially overwrite any other # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure # that source layer fields and attributes are present in the context context.appendScope( self.source_layer.createExpressionContextScope()) self.settings.data_defined_properties.prepare(context) self.fetch_layout_properties(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.visible_features_only and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.visible_features_only and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) # Some plot types don't draw individual glyphs for each feature, but aggregate them instead. # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each # feature. Instead, the evaluation should be executed only once for these settings. aggregating = self.settings.plot_type in ['box', 'histogram'] executed = False xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['in_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead colors.append(default_value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['out_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color stroke_colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_STROKE_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] stroke_colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead stroke_colors.append(default_value.name()) executed = True self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
def fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() self.settings.data_defined_properties.prepare(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.visible_features_only and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.visible_features_only and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR): default_value = QColor(self.settings.properties['in_color']) value, _ = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) colors.append(value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR): default_value = QColor(self.settings.properties['out_color']) value, _ = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) stroke_colors.append(value.name()) self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
def runGetFeatureTests(self, provider): assert len([f for f in provider.getFeatures()]) == 5 self.assert_query(provider, 'name ILIKE \'QGIS\'', []) self.assert_query(provider, '"name" IS NULL', [5]) self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4]) self.assert_query(provider, 'name = \'Apple\'', [2]) self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'name = \'apple\'', []) self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4]) self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4]) self.assert_query(provider, 'name LIKE \'Apple\'', [2]) self.assert_query(provider, 'name LIKE \'aPple\'', []) self.assert_query(provider, 'name ILIKE \'aPple\'', [2]) self.assert_query(provider, 'name ILIKE \'%pp%\'', [2]) self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4]) self.assert_query(provider, '-cnt > 0', [5]) self.assert_query(provider, 'cnt < 0', [5]) self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4]) self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4]) self.assert_query(provider, 'cnt <= 100', [1, 5]) self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4]) self.assert_query(provider, 'cnt = 50 * 2', [1]) self.assert_query(provider, 'cnt = 99 + 1', [1]) self.assert_query(provider, 'cnt = 101 - 1', [1]) self.assert_query(provider, 'cnt - 1 = 99', [1]) self.assert_query(provider, '-cnt - 1 = -101', [1]) self.assert_query(provider, '-(-cnt) = 100', [1]) self.assert_query(provider, '-(cnt) = -(100)', [1]) self.assert_query(provider, 'cnt + 1 = 101', [1]) self.assert_query(provider, 'cnt = 1100 % 1000', [1]) self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1]) self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1]) self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '\'x\' || "name" IS NULL', [5]) self.assert_query(provider, 'cnt = 10 ^ 2', [1]) self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1]) self.assert_query(provider, '"name"="name2"', [2, 4]) # mix of matched and non-matched case sensitive names self.assert_query(provider, 'true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false', []) # Three value logic self.assert_query(provider, 'false and false', []) self.assert_query(provider, 'false and true', []) self.assert_query(provider, 'false and NULL', []) self.assert_query(provider, 'true and false', []) self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true and NULL', []) self.assert_query(provider, 'NULL and false', []) self.assert_query(provider, 'NULL and true', []) self.assert_query(provider, 'NULL and NULL', []) self.assert_query(provider, 'false or false', []) self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false or NULL', []) self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or false', []) self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or NULL', []) self.assert_query(provider, 'not true', []) self.assert_query(provider, 'not false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'not null', []) # not self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4]) self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3]) self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5]) # type conversion - QGIS expressions do not mind that we are comparing a string # against numeric literals self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5]) # geometry self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2]) # combination of an uncompilable expression and limit feature = next(self.vl.getFeatures('pk=4')) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('parent', feature) context.appendScope(scope) request = QgsFeatureRequest() request.setExpressionContext(context) request.setFilterExpression('"pk" = attribute(@parent, \'pk\')') request.setLimit(1) values = [f['pk'] for f in self.vl.getFeatures(request)] self.assertEqual(values, [4])
def runGetFeatureTests(self, provider): assert len([f for f in provider.getFeatures()]) == 5 self.assert_query(provider, 'name ILIKE \'QGIS\'', []) self.assert_query(provider, '"name" IS NULL', [5]) self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4]) self.assert_query(provider, 'name = \'Apple\'', [2]) self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'name = \'apple\'', []) self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4]) self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4]) self.assert_query(provider, 'name LIKE \'Apple\'', [2]) self.assert_query(provider, 'name LIKE \'aPple\'', []) self.assert_query(provider, 'name ILIKE \'aPple\'', [2]) self.assert_query(provider, 'name ILIKE \'%pp%\'', [2]) self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4]) self.assert_query(provider, '-cnt > 0', [5]) self.assert_query(provider, 'cnt < 0', [5]) self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4]) self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4]) self.assert_query(provider, 'cnt <= 100', [1, 5]) self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4]) self.assert_query(provider, 'cnt = 50 * 2', [1]) self.assert_query(provider, 'cnt = 99 + 1', [1]) self.assert_query(provider, 'cnt = 101 - 1', [1]) self.assert_query(provider, 'cnt - 1 = 99', [1]) self.assert_query(provider, '-cnt - 1 = -101', [1]) self.assert_query(provider, '-(-cnt) = 100', [1]) self.assert_query(provider, '-(cnt) = -(100)', [1]) self.assert_query(provider, 'cnt + 1 = 101', [1]) self.assert_query(provider, 'cnt = 1100 % 1000', [1]) self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1]) self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1]) self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '\'x\' || "name" IS NULL', [5]) self.assert_query(provider, 'cnt = 10 ^ 2', [1]) self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1]) self.assert_query( provider, '"name"="name2"', [2, 4]) # mix of matched and non-matched case sensitive names self.assert_query(provider, 'true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false', []) # Three value logic self.assert_query(provider, 'false and false', []) self.assert_query(provider, 'false and true', []) self.assert_query(provider, 'false and NULL', []) self.assert_query(provider, 'true and false', []) self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true and NULL', []) self.assert_query(provider, 'NULL and false', []) self.assert_query(provider, 'NULL and true', []) self.assert_query(provider, 'NULL and NULL', []) self.assert_query(provider, 'false or false', []) self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false or NULL', []) self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or false', []) self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or NULL', []) self.assert_query(provider, 'not true', []) self.assert_query(provider, 'not false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'not null', []) # not self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4]) self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3]) self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5]) # type conversion - QGIS expressions do not mind that we are comparing a string # against numeric literals self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5]) # geometry self.assert_query( provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2]) # combination of an uncompilable expression and limit feature = next(self.vl.getFeatures('pk=4')) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('parent', feature) context.appendScope(scope) request = QgsFeatureRequest() request.setExpressionContext(context) request.setFilterExpression('"pk" = attribute(@parent, \'pk\')') request.setLimit(1) values = [f['pk'] for f in self.vl.getFeatures(request)] self.assertEqual(values, [4])
def runGetFeatureTests(self, provider): assert len([f for f in provider.getFeatures()]) == 5 self.assert_query(provider, 'name ILIKE \'QGIS\'', []) self.assert_query(provider, '"name" IS NULL', [5]) self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4]) self.assert_query(provider, 'name = \'Apple\'', [2]) self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'name = \'apple\'', []) self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4]) self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4]) self.assert_query(provider, 'name LIKE \'Apple\'', [2]) self.assert_query(provider, 'name LIKE \'aPple\'', []) self.assert_query(provider, 'name ILIKE \'aPple\'', [2]) self.assert_query(provider, 'name ILIKE \'%pp%\'', [2]) self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4]) self.assert_query(provider, '-cnt > 0', [5]) self.assert_query(provider, 'cnt < 0', [5]) self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4]) self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4]) self.assert_query(provider, 'cnt <= 100', [1, 5]) self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4]) self.assert_query(provider, 'cnt = 50 * 2', [1]) self.assert_query(provider, 'cnt = 150 / 1.5', [1]) self.assert_query(provider, 'cnt = 1000 / 10', [1]) self.assert_query(provider, 'cnt = 1000/11+10', []) # checks that provider isn't rounding int/int self.assert_query(provider, 'pk = 9 // 4', [2]) # int division self.assert_query(provider, 'cnt = 99 + 1', [1]) self.assert_query(provider, 'cnt = 101 - 1', [1]) self.assert_query(provider, 'cnt - 1 = 99', [1]) self.assert_query(provider, '-cnt - 1 = -101', [1]) self.assert_query(provider, '-(-cnt) = 100', [1]) self.assert_query(provider, '-(cnt) = -(100)', [1]) self.assert_query(provider, 'cnt + 1 = 101', [1]) self.assert_query(provider, 'cnt = 1100 % 1000', [1]) self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1]) self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1]) self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '\'x\' || "name" IS NULL', [5]) self.assert_query(provider, 'cnt = 10 ^ 2', [1]) self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1]) self.assert_query(provider, '"name"="name2"', [2, 4]) # mix of matched and non-matched case sensitive names self.assert_query(provider, 'true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false', []) # Three value logic self.assert_query(provider, 'false and false', []) self.assert_query(provider, 'false and true', []) self.assert_query(provider, 'false and NULL', []) self.assert_query(provider, 'true and false', []) self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true and NULL', []) self.assert_query(provider, 'NULL and false', []) self.assert_query(provider, 'NULL and true', []) self.assert_query(provider, 'NULL and NULL', []) self.assert_query(provider, 'false or false', []) self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false or NULL', []) self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or false', []) self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or NULL', []) self.assert_query(provider, 'not true', []) self.assert_query(provider, 'not false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'not null', []) # not self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4]) self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3]) self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5]) # type conversion - QGIS expressions do not mind that we are comparing a string # against numeric literals self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5]) #function self.assert_query(provider, 'sqrt(pk) >= 2', [4, 5]) self.assert_query(provider, 'radians(cnt) < 2', [1, 5]) self.assert_query(provider, 'degrees(pk) <= 200', [1, 2, 3]) self.assert_query(provider, 'abs(cnt) <= 200', [1, 2, 5]) self.assert_query(provider, 'cos(pk) < 0', [2, 3, 4]) self.assert_query(provider, 'sin(pk) < 0', [4, 5]) self.assert_query(provider, 'tan(pk) < 0', [2, 3, 5]) self.assert_query(provider, 'acos(-1) < pk', [4, 5]) self.assert_query(provider, 'asin(1) < pk', [2, 3, 4, 5]) self.assert_query(provider, 'atan(3.14) < pk', [2, 3, 4, 5]) self.assert_query(provider, 'atan2(3.14, pk) < 1', [3, 4, 5]) self.assert_query(provider, 'exp(pk) < 10', [1, 2]) self.assert_query(provider, 'ln(pk) <= 1', [1, 2]) self.assert_query(provider, 'log(3, pk) <= 1', [1, 2, 3]) self.assert_query(provider, 'log10(pk) < 0.5', [1, 2, 3]) self.assert_query(provider, 'round(3.14) <= pk', [3, 4, 5]) self.assert_query(provider, 'round(0.314,1) * 10 = pk', [3]) self.assert_query(provider, 'floor(3.14) <= pk', [3, 4, 5]) self.assert_query(provider, 'ceil(3.14) <= pk', [4, 5]) self.assert_query(provider, 'pk < pi()', [1, 2, 3]) self.assert_query(provider, 'round(cnt / 66.67) <= 2', [1, 5]) self.assert_query(provider, 'floor(cnt / 66.67) <= 2', [1, 2, 5]) self.assert_query(provider, 'ceil(cnt / 66.67) <= 2', [1, 5]) self.assert_query(provider, 'pk < pi() / 2', [1]) self.assert_query(provider, 'pk = char(51)', [3]) self.assert_query(provider, 'pk = coalesce(NULL,3,4)', [3]) self.assert_query(provider, 'lower(name) = \'apple\'', [2]) self.assert_query(provider, 'upper(name) = \'APPLE\'', [2]) self.assert_query(provider, 'name = trim(\' Apple \')', [2]) # geometry # azimuth and touches tests are deactivated because they do not pass for WFS provider #self.assert_query(provider, 'azimuth($geometry,geom_from_wkt( \'Point (-70 70)\')) < pi()', [1, 5]) self.assert_query(provider, 'x($geometry) < -70', [1, 5]) self.assert_query(provider, 'y($geometry) > 70', [2, 4, 5]) self.assert_query(provider, 'xmin($geometry) < -70', [1, 5]) self.assert_query(provider, 'ymin($geometry) > 70', [2, 4, 5]) self.assert_query(provider, 'xmax($geometry) < -70', [1, 5]) self.assert_query(provider, 'ymax($geometry) > 70', [2, 4, 5]) self.assert_query(provider, 'disjoint($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [4, 5]) self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2]) #self.assert_query(provider, 'touches($geometry,geom_from_wkt( \'Polygon ((-70.332 66.33, -65.32 66.33, -65.32 78.3, -70.332 78.3, -70.332 66.33))\'))', [1, 4]) self.assert_query(provider, 'contains(geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'),$geometry)', [1, 2]) self.assert_query(provider, 'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7', [4, 5]) self.assert_query(provider, 'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))', [1, 2]) # combination of an uncompilable expression and limit feature = next(self.vl.getFeatures('pk=4')) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('parent', feature) context.appendScope(scope) request = QgsFeatureRequest() request.setExpressionContext(context) request.setFilterExpression('"pk" = attribute(@parent, \'pk\')') request.setLimit(1) values = [f['pk'] for f in self.vl.getFeatures(request)] self.assertEqual(values, [4])
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) request.setSubsetOfAttributes(self.fields, source.fields()) request.addOrderBy("|| ' - ' || ".join(self.fields)) unique_couples = [] non_unique_couples = [] for src_feature in source.getFeatures(request): couple = (src_feature[self.fields[0]], src_feature[self.fields[1]]) if couple not in unique_couples: unique_couples.append(couple) else: non_unique_couples.append(couple) if not non_unique_couples: feedback.pushInfo( 'L\'ensemble des couples noms/faciès sont uniques') _, dest_id = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) return { self.OUTPUT: dest_id, self.NUMBER_OF_UNIQUE: len(unique_couples), self.NUMBER_OF_NON_UNIQUE: 0 } feedback.pushInfo('Certains couples ne sont pas uniques :') for couple in non_unique_couples: feedback.pushInfo(' {} - {}'.format(couple[0], couple[1])) expressions = [] for couple in non_unique_couples: exp = ' AND '.join([ QgsExpression.createFieldEqualityExpression( self.fields[0], couple[0]), QgsExpression.createFieldEqualityExpression( self.fields[1], couple[1]) ]) expressions.append(exp) exp = '(' exp += ') OR ('.join(expressions) exp += ')' feedback.pushDebugInfo(exp) exp_context = self.createExpressionContext(parameters, context, source) request = QgsFeatureRequest() request.setFilterExpression(exp) request.setExpressionContext(exp_context) layer = source.materialize(request) params = { 'ALL_PARTS': True, 'INPUT': layer, 'OUTPUT': 'TEMPORARY_OUTPUT' } results = processing.run( "native:pointonsurface", params, context=context, feedback=feedback, is_child_algorithm=True, ) params = { 'INPUT': results['OUTPUT'], 'FIELD': self.fields, 'OUTPUT': parameters[self.OUTPUT] } results = processing.run( "native:collect", params, context=context, feedback=feedback, is_child_algorithm=True, ) output_layer = results['OUTPUT'] if context.willLoadLayerOnCompletion(output_layer): layer_details = context.layerToLoadOnCompletionDetails( output_layer) output_def = self.parameterDefinition(self.OUTPUT) layer_details.name = output_def.description() layer_details.setPostProcessor( SetLabelingPostProcessor.create(self.fields)) return { self.OUTPUT: output_layer, self.NUMBER_OF_UNIQUE: len(unique_couples), self.NUMBER_OF_NON_UNIQUE: len(non_unique_couples) }