def testSubsetAttributes(self): req = QgsFeatureRequest() self.assertFalse(req.subsetOfAttributes()) self.assertFalse(req.flags() & QgsFeatureRequest.SubsetOfAttributes) req.setSubsetOfAttributes([1, 4]) self.assertEqual(req.subsetOfAttributes(), [1, 4]) self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes) req.setNoAttributes() self.assertEqual(req.subsetOfAttributes(), []) self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes) req.setSubsetOfAttributes([]) self.assertFalse(req.subsetOfAttributes()) self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes) req.setFlags(QgsFeatureRequest.Flags()) f = QgsFields() f.append(QgsField('a', QVariant.String)) f.append(QgsField('b', QVariant.String)) f.append(QgsField('c', QVariant.String)) req.setSubsetOfAttributes(['a', 'c'], f) self.assertEqual(req.subsetOfAttributes(), [0, 2]) self.assertTrue(req.flags() & QgsFeatureRequest.SubsetOfAttributes)
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.spatialFilterType(), Qgis.SpatialFilterType.BoundingBox) 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()) # copy distance within request req = QgsFeatureRequest().setDistanceWithin( QgsGeometry.fromWkt('LineString( 0 0, 10 0, 11 2)'), 1.2) req2 = QgsFeatureRequest(req) self.assertEqual(req2.spatialFilterType(), Qgis.SpatialFilterType.DistanceWithin) self.assertEqual(req2.referenceGeometry().asWkt(), 'LineString (0 0, 10 0, 11 2)') self.assertEqual(req2.distanceWithin(), 1.2) self.assertEqual(req2.filterRect(), QgsRectangle(-1.2, -1.2, 12.2, 3.2))
def response_data_mode(self, request, export_features=False): """ Query layer and return data :param request: DjangoREST API request object :param formatter: Boolean, default False, True for to use QgsJsonExport.exportFeatures method :return: response dict data """ # Create the QGIS feature request, it will be passed through filters # and to the final QGIS API get features call. qgis_feature_request = QgsFeatureRequest() # Prepare arguments for the get feature call kwargs = {} # Apply filter backends, store original subset string original_subset_string = self.metadata_layer.qgis_layer.subsetString() if hasattr(self, 'filter_backends'): try: for backend in self.filter_backends: backend().apply_filter(request, self.metadata_layer, qgis_feature_request, self) except Exception as e: raise APIException(e) # Paging cannot be a backend filter if 'page' in request.query_params: kwargs['page'] = request.query_params.get('page') kwargs['page_size'] = request.query_params.get('page_size', 10) # Make sure we have all attrs we need to build the server FID provider = self.metadata_layer.qgis_layer.dataProvider() if qgis_feature_request.flags() & QgsFeatureRequest.SubsetOfAttributes: attrs = qgis_feature_request.subsetOfAttributes() for attr_idx in provider.pkAttributeIndexes(): if attr_idx not in attrs: attrs.append(attr_idx) qgis_feature_request.setSubsetOfAttributes(attrs) self.features = get_qgis_features(self.metadata_layer.qgis_layer, qgis_feature_request, **kwargs) # Reproject feature if layer CRS != Project CRS if self.reproject: for f in self.features: self.reproject_feature(f) ex = QgsJsonExporter(self.metadata_layer.qgis_layer) # If 'unique' request params is set, # api return a list of unique # field name sent with 'unique' param. # -------------------------------------- # IDEA: for big data it'll be iterate over features to get unique # c++ iteration is fast. Instead memory layer with too many features can be a problem. if 'unique' in request.query_params: vl = QgsVectorLayer( QgsWkbTypes.displayString( self.metadata_layer.qgis_layer.wkbType()), "temporary_vector", "memory") pr = vl.dataProvider() # add fields pr.addAttributes(self.metadata_layer.qgis_layer.fields()) vl.updateFields( ) # tell the vector layer to fetch changes from the provider res = pr.addFeatures(self.features) uniques = vl.uniqueValues( self.metadata_layer.qgis_layer.fields().indexOf( request.query_params.get('unique'))) values = [] for u in uniques: try: if u: values.append(json.loads(QgsJsonUtils.encodeValue(u))) except Exception as e: logger.error(f'Response vector widget unique: {e}') continue # sort values values.sort() self.results.update({'data': values, 'count': len(values)}) del (vl) else: ex.setTransformGeometries(False) # check for formatter query url param and check if != 0 if 'formatter' in request.query_params: formatter = request.query_params.get('formatter') if formatter.isnumeric() and int(formatter) == 0: export_features = False else: export_features = True if export_features: feature_collection = json.loads( ex.exportFeatures(self.features)) else: # to exclude QgsFormater used into QgsJsonExporter is necessary build by hand single json feature ex.setIncludeAttributes(False) feature_collection = { 'type': 'FeatureCollection', 'features': [] } for feature in self.features: fnames = [] date_fields = [] for f in feature.fields(): fnames.append(f.name()) if f.typeName() in ('date', 'datetime', 'time'): date_fields.append(f) jsonfeature = json.loads( ex.exportFeature( feature, dict(zip(fnames, feature.attributes())))) # Update date and datetime fields value if widget is active if len(date_fields) > 0: for f in date_fields: field_idx = self.metadata_layer.qgis_layer.fields( ).indexFromName(f.name()) options = self.metadata_layer.qgis_layer.editorWidgetSetup( field_idx).config() if 'field_iso_format' in options and not options[ 'field_iso_format']: try: jsonfeature['properties'][f.name()] = feature.attribute(f.name())\ .toString(options['field_format']) except: pass feature_collection['features'].append(jsonfeature) # Change media self.change_media(feature_collection) # Patch feature IDs with server featureIDs fids_map = {} for f in self.features: fids_map[f.id()] = server_fid(f, provider) for i in range(len(feature_collection['features'])): f = feature_collection['features'][i] f['id'] = fids_map[f['id']] self.results.update( APIVectorLayerStructure( **{ 'data': feature_collection, 'count': count_qgis_features(self.metadata_layer.qgis_layer, qgis_feature_request, **kwargs), 'geometryType': self.metadata_layer.geometry_type, }).as_dict()) # FIXME: add extra fields data by signals and receivers # FIXME: featurecollection = post_serialize_maplayer.send(layer_serializer, layer=self.layer_name) # FIXME: Not sure how to map this to the new QGIS API # Restore the original subset string self.metadata_layer.qgis_layer.setSubsetString(original_subset_string)