def testGetQgisFeaturesAttributeFilters(self): """Test QGIS API attribute filters""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) features = get_qgis_features(qgis_layer, attribute_filters={'name': 'a point'}) self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 1) features = get_qgis_features( qgis_layer, attribute_filters={'name': 'another point'}) self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 2) features = get_qgis_features(qgis_layer, attribute_filters={ 'name': 'another point', 'pkuid': 2 }) self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 2) features = get_qgis_features(qgis_layer, attribute_filters={ 'name': 'another point', 'pkuid': 9999 }) self.assertEqual(len(features), 0) features = get_qgis_features( qgis_layer, attribute_filters={'name': 'not_existent'}) self.assertEqual(len(features), 0)
def testGetQgisFeaturesExcludeFields(self): """Test QGIS API get_qgis_features with exclude fields filter""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) features = get_qgis_features(qgis_layer, exclude_fields=['pkuid']) self.assertEqual(len(features), 2) self.assertIsNone(features[0].attribute('pkuid')) self.assertIsNotNone(features[0].attribute('name')) features = get_qgis_features(qgis_layer, exclude_fields=['name']) self.assertEqual(len(features), 2) self.assertIsNotNone(features[0].attribute('pkuid')) self.assertIsNone(features[0].attribute('name'))
def testGetQgisFeaturesBbox(self): """Test QGIS API get_qgis_features with BBOX filter""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) features = get_qgis_features(qgis_layer, bbox_filter=qgis_layer.extent()) self.assertEqual(len(features), 2) bbox = QgsRectangle(qgis_layer.extent().xMinimum(), qgis_layer.extent().yMinimum(), qgis_layer.extent().xMinimum() + 1, qgis_layer.extent().yMinimum() + 1) features = get_qgis_features(qgis_layer, bbox_filter=bbox) self.assertEqual(len(features), 1)
def testGetQgisFeaturesAll(self): """Test QGIS API get_qgis_features with all features""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) # Test get all features features = get_qgis_features(qgis_layer) self.assertEqual(len(features), qgis_layer.featureCount()) # Check has geometry self.assertFalse(features[0].geometry().isNull()) # Get all features, no geometry features = get_qgis_features(qgis_layer, with_geometry=False) self.assertEqual(len(features), qgis_layer.featureCount()) # Check has geometry self.assertTrue(features[0].geometry().isNull())
def testGetQgisFeaturesSearch(self): """Test QGIS API get_qgis_features with search filter""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) features = get_qgis_features(qgis_layer, search_filter='1') self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 1) features = get_qgis_features(qgis_layer, search_filter='another') self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 2) features = get_qgis_features(qgis_layer, search_filter='point') self.assertEqual(len(features), 2) # not existent features = get_qgis_features(qgis_layer, search_filter='not_exists') self.assertEqual(len(features), 0)
def testGetQgisFeaturesCombinedFilters(self): """Test QGIS API attribute combined filters""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) features = get_qgis_features(qgis_layer, attribute_filters={ 'name': 'a point' }, search_filter='not_existent') self.assertEqual(len(features), 0) features = get_qgis_features(qgis_layer, attribute_filters={ 'name': 'another point' }, search_filter='1') self.assertEqual(len(features), 0) features = get_qgis_features(qgis_layer, attribute_filters={ 'name': 'another point' }, search_filter='2') self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 2)
def testGetQgisFeaturesPagination(self): """Test QGIS API get_qgis_features with pagination""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) # Test get first page features = get_qgis_features(qgis_layer, page=1, page_size=1) self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 1) # second page features = get_qgis_features(qgis_layer, page=1, page_size=1) self.assertEqual(len(features), 1) self.assertTrue(features[0].id(), 2) # out of range offset features = get_qgis_features(qgis_layer, page=100, page_size=1) self.assertEqual(len(features), 0) # out of range feature_count features = get_qgis_features(qgis_layer, page_size=1000) self.assertEqual(len(features), 2)
def testGetQgisFeaturesOrdering(self): """Test QGIS API get_qgis_features with ordering""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) # Test get first page features = get_qgis_features(qgis_layer, ordering='name') self.assertEqual(len(features), 2) self.assertTrue(features[0].id(), 1) # second page features = get_qgis_features(qgis_layer, ordering='name') self.assertEqual(len(features), 2) self.assertTrue(features[0].id(), 2) # Test get first page features = get_qgis_features(qgis_layer, ordering='-name') self.assertEqual(len(features), 2) self.assertTrue(features[0].id(), 2) # not existent field (ignored) features = get_qgis_features(qgis_layer, ordering='not_exists') self.assertEqual(len(features), 2)
def get_constraint_geometry(self): """Returns the geometry from the constraint layer and rule :return: the constraint geometry and the number of matched records :rtype: tuple( MultiPolygon, integer) """ constraint_layer = get_qgis_layer(self.constraint.constraint_layer) layer = get_qgis_layer(self.constraint.layer) # Get the geometries from constraint layer and rule qgis_feature_request = QgsFeatureRequest() qgis_feature_request.setFilterExpression(self.rule) features = get_qgis_features(constraint_layer, qgis_feature_request, exclude_fields='__all__') if not features: return '', 0 geometry = QgsMultiPolygon() for feature in features: geom = feature.geometry() if geom.isMultipart(): geom = [g for g in geom.constGet()] else: geom = [geom.constGet()] i = 0 for g in geom: geometry.insertGeometry(g.clone(), 0) i += 1 # Now, transform into a GEOS geometry if constraint_layer.crs() != layer.crs(): ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem(constraint_layer.crs()), QgsCoordinateReferenceSystem(layer.crs()), QgsCoordinateTransformContext()) geometry.transform(ct) constraint_geometry = MultiPolygon.from_ewkt( 'SRID=%s;' % layer.crs().postgisSrid() + geometry.asWkt()) return constraint_geometry, constraint_geometry.num_geom
def testGetQgisFeaturesExtraSubsetString(self): """Test QGIS API get_qgis_features with subset string filter""" qgis_layer = get_qgis_layer(self.layer) self.assertTrue(qgis_layer.isValid()) features = get_qgis_features(qgis_layer) self.assertEqual(len(features), 2) features = get_qgis_features( qgis_layer, extra_subset_string='name != \'another point\'') self.assertEqual(len(features), 1) self.assertEqual(features[0]['name'], 'a point') # Check if restored features = get_qgis_features(qgis_layer) self.assertEqual(len(features), 2) features = get_qgis_features( qgis_layer, extra_subset_string='name == \'another point\'') self.assertEqual(len(features), 1) self.assertEqual(features[0]['name'], 'another point') # Check if original subset string is ANDed qgis_layer_clone = qgis_layer.clone() qgis_layer_clone.setSubsetString('name == \'another point\'') features = get_qgis_features(qgis_layer_clone) self.assertEqual(len(features), 1) self.assertEqual(features[0]['name'], 'another point') features = get_qgis_features( qgis_layer_clone, extra_subset_string='name != \'another point\'') self.assertEqual(len(features), 0) # Check if restored qgis_layer_clone.setSubsetString('name == \'another point\'') features = get_qgis_features(qgis_layer_clone) self.assertEqual(len(features), 1) self.assertEqual(features[0]['name'], 'another point')
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'): for backend in self.filter_backends: backend().apply_filter(request, self.metadata_layer.qgis_layer, qgis_feature_request, self) # 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) self.features = get_qgis_features( self.metadata_layer.qgis_layer, qgis_feature_request, **kwargs) 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 fith to much 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 self.results.update({ 'data': values, 'count': len(values) }) del(vl) else: # patch for return GeoJson feature with CRS different from WGS84 # TODO: use .setTransformGeometries( false ) with QGIS >= 3.12 ex.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326')) # 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 QgsJsonjExporter is necessary build by hand single json feature ex.setIncludeAttributes(False) feature_collection = { 'type': 'FeatureCollection', 'features': [] } for feature in self.features: fnames = [f.name() for f in feature.fields()] feature_collection['features'].append( json.loads(ex.exportFeature(feature, dict(zip(fnames, feature.attributes())))) ) # FIXME: QGIS api reprojecting? # Reproject if necessary # if self.reproject: # self.reproject_featurecollection(feature_collection) # Change media self.change_media(feature_collection) 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)
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)