def testStringToFields(self): """test retrieving fields from GeoJSON strings""" # empty string fields = QgsJsonUtils.stringToFields("", codec) self.assertEqual(fields.count(), 0) # bad string fields = QgsJsonUtils.stringToFields("asdasdas", codec) self.assertEqual(fields.count(), 0) # geojson string fields = QgsJsonUtils.stringToFields('{\n"type": "Feature","geometry": {"type": "Point","coordinates": [125, 10]},"properties": {"name": "Dinagat Islands","height":5.5}}', codec) self.assertEqual(fields.count(), 2) self.assertEqual(fields[0].name(), "name") self.assertEqual(fields[0].type(), QVariant.String) self.assertEqual(fields[1].name(), "height") self.assertEqual(fields[1].type(), QVariant.Double)
def testStringToFeatureList(self): """Test converting json string to features""" fields = QgsFields() fields.append(QgsField("name", QVariant.String)) # empty string features = QgsJsonUtils.stringToFeatureList("", fields, codec) self.assertEqual(features, []) # bad string features = QgsJsonUtils.stringToFeatureList("asdasdas", fields, codec) self.assertEqual(features, []) # geojson string with 1 feature features = QgsJsonUtils.stringToFeatureList('{\n"type": "Feature","geometry": {"type": "Point","coordinates": [125, 10]},"properties": {"name": "Dinagat Islands"}}', fields, codec) self.assertEqual(len(features), 1) self.assertFalse(features[0].geometry().isNull()) self.assertEqual(features[0].geometry().wkbType(), QgsWkbTypes.Point) point = features[0].geometry().constGet() self.assertEqual(point.x(), 125.0) self.assertEqual(point.y(), 10.0) self.assertEqual(features[0]['name'], "Dinagat Islands") # geojson string with 2 features features = QgsJsonUtils.stringToFeatureList('{ "type": "FeatureCollection","features":[{\n"type": "Feature","geometry": {"type": "Point","coordinates": [125, 10]},"properties": {"name": "Dinagat Islands"}}, {\n"type": "Feature","geometry": {"type": "Point","coordinates": [110, 20]},"properties": {"name": "Henry Gale Island"}}]}', fields, codec) self.assertEqual(len(features), 2) self.assertFalse(features[0].geometry().isNull()) self.assertEqual(features[0].geometry().wkbType(), QgsWkbTypes.Point) point = features[0].geometry().constGet() self.assertEqual(point.x(), 125.0) self.assertEqual(point.y(), 10.0) self.assertEqual(features[0]['name'], "Dinagat Islands") self.assertFalse(features[1].geometry().isNull()) self.assertEqual(features[1].geometry().wkbType(), QgsWkbTypes.Point) point = features[1].geometry().constGet() self.assertEqual(point.x(), 110.0) self.assertEqual(point.y(), 20.0) self.assertEqual(features[1]['name'], "Henry Gale Island")
def testExportAttributes(self): """ test exporting feature's attributes to JSON object """ fields = QgsFields() # test empty attributes feature = QgsFeature(fields, 5) expected = "{}" self.assertEqual(QgsJsonUtils.exportAttributes(feature), expected) # test feature with attributes fields.append(QgsField("name", QVariant.String)) fields.append(QgsField("cost", QVariant.Double)) fields.append(QgsField("population", QVariant.Int)) feature = QgsFeature(fields, 5) feature.setGeometry(QgsGeometry(QgsPoint(5, 6))) feature.setAttributes(['Valsier Peninsula', 6.8, 198]) expected = """{"name":"Valsier Peninsula", "cost":6.8, "population":198}""" self.assertEqual(QgsJsonUtils.exportAttributes(feature), expected) # test using field formatters source = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer", "parent", "memory") pf1 = QgsFeature() pf1.setFields(source.fields()) pf1.setAttributes(["test1", 1]) setup = QgsEditorWidgetSetup('ValueMap', {"map": {"one": 1, "two": 2, "three": 3}}) source.setEditorWidgetSetup(1, setup) expected = """{"fldtxt":"test1", "fldint":"one"}""" self.assertEqual(QgsJsonUtils.exportAttributes(pf1, source), expected)
def qgsfeature_feature_from_node(self, node: PlanetNode): # TODO: Resolve geometry by node_type or do that under node.geometry()? # geom = None # if node.node_type() == NodeT.DAILY_SCENE_IMAGE: # geom = node.geometry() # TODO: Add node # feature_collect = { # 'type': 'FeatureCollection', # 'features': [ # { # 'type': 'Feature', # 'geometry': node.geometry(), # 'properties': { # 'id': node.item_id() # } # } # ] # } feature_collect = { 'type': 'FeatureCollection', 'features': [ node.resource() ] } feature_collect_json = json.dumps(feature_collect) # noinspection PyUnusedLocal features = [] # noinspection PyBroadException try: utf8 = QTextCodec.codecForName('UTF-8') # TODO: Add node id, properties as fields? fields = QgsFields() features = QgsJsonUtils().stringToFeatureList( string=feature_collect_json, fields=fields, encoding=utf8) except Exception: log.debug('Footprint GeoJSON could not be parsed') return if not len(features) > 0: log.debug('GeoJSON parsing created no features') return return features[0]
def getFeaturesFromResponse(response): """Return a list of features from a valhalla response object """ fields = QgsFields() fields.append(QgsField("opacity", QVariant.Double)) fields.append(QgsField("fill", QVariant.String)) fields.append(QgsField("fillOpacity", QVariant.Double)) fields.append(QgsField("fill-opacity", QVariant.Double)) # FIXME: in fact, due to a bug in qgis parser, we cannot use this field fields.append(QgsField("contour", QVariant.Int)) fields.append(QgsField("color", QVariant.String)) fields.append(QgsField("fillColor", QVariant.String)) codec = QTextCodec.codecForName("UTF-8") features = QgsJsonUtils.stringToFeatureList(json.dumps(response), fields, codec) # LOG.debug('features : {}'.format(features)) return features
def _single_feature(feat): # existing feature json if feat is None: return None obj = {"type": "Feature"} json_str = QgsJsonUtils.exportAttributes(feat) props = json.loads(json_str) props.pop(QGS_ID, None) v = props.pop(QGS_XYZ_ID, None) if v is not None and v != "": if v in exist_feat_id: return None exist_feat_id.add(v) if not is_new: obj[XYZ_ID] = v fields = feat.fields() expression_field_names = [ f.name() for i, f in enumerate(fields) if fields.fieldOrigin(i) == fields.OriginExpression ] # print({k.name(): fields.fieldOrigin(i) for i, k in enumerate(fields)}) props = _xyz_props(props, ignore_keys=expression_field_names) livemap_props = _livemap_props(props, xyz_id=obj.get(XYZ_ID)) if is_livemap else dict() props = _clean_props(props) obj["properties"] = dict(props, **livemap_props) geom = feat.geometry() geom_ = json.loads(geom.asJson()) if geom_ is None: # print_qgis(obj) return obj obj["geometry"] = make_valid_xyz_json_geom(geom_) # bbox = geom.boundingBox() # print_qgis("bbox: %s"%bbox.toString()) if bbox.isEmpty(): # if "coordinates" in geom_: obj["bbox"] = list(geom_["coordinates"]) * 2 else: obj[ # "bbox"] = [bbox.xMinimum(), bbox.yMinimum(), 0.0, bbox.xMaximum(), bbox.yMaximum(), # 0.0] obj["bbox"] = [bbox.xMinimum(), bbox.yMinimum(), bbox.xMaximum(), bbox.yMaximum()] return obj
def addFeature(self): text = self.lineEdit.text().strip() if text == "": return layer = self.iface.activeLayer() if layer is None: return try: if (self.inputProjection == 0) or (text[0] == '{'): # If this is GeoJson it does not matter what inputProjection is if text[0] == '{': # This may be a GeoJSON point codec = QTextCodec.codecForName("UTF-8") fields = QgsJsonUtils.stringToFields(text, codec) fet = QgsJsonUtils.stringToFeatureList(text, fields, codec) if (len(fet) == 0) or not fet[0].isValid(): raise ValueError('Invalid Coordinates') geom = fet[0].geometry() if geom.isEmpty() or (geom.wkbType() != QgsWkbTypes.Point): raise ValueError('Invalid GeoJSON Geometry') pt = geom.asPoint() lat = pt.y() lon = pt.x() elif re.search(r'POINT\(', text) is not None: m = re.findall( r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) else: lat, lon = LatLon.parseDMSString(text, self.inputXYOrder) srcCrs = epsg4326 elif self.inputProjection == 1: # This is an MGRS coordinate text = re.sub(r'\s+', '', unicode(text)) # Remove all white space lat, lon = mgrs.toWgs(text) srcCrs = epsg4326 elif self.inputProjection == 4: text = text.strip() coord = olc.decode(text) lat = coord.latitudeCenter lon = coord.longitudeCenter srcCrs = epsg4326 else: # Is either the project or custom CRS if re.search(r'POINT\(', text) is None: coords = re.split(r'[\s,;:]+', text, 1) if len(coords) < 2: raise ValueError('Invalid Coordinates') if self.inputXYOrder == 0: # Y, X Order lat = float(coords[0]) lon = float(coords[1]) else: lon = float(coords[0]) lat = float(coords[1]) else: m = re.findall( r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) if self.inputProjection == 2: # Project CRS srcCrs = self.canvas.mapSettings().destinationCrs() else: srcCrs = QgsCoordinateReferenceSystem(self.inputCustomCRS) except Exceptions: # traceback.print_exc() self.iface.messageBar().pushMessage("", "Invalid Coordinate", level=Qgis.Warning, duration=2) return self.lineEdit.clear() caps = layer.dataProvider().capabilities() if caps & QgsVectorDataProvider.AddFeatures: destCRS = layer.crs( ) # Get the CRS of the layer we are adding a point toWgs transform = QgsCoordinateTransform(srcCrs, destCRS, QgsProject.instance()) # Transform the input coordinate projection to the layer CRS x, y = transform.transform(float(lon), float(lat)) feat = QgsFeature(layer.fields()) feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y))) if layer.fields().count() == 0: layer.addFeature(feat) self.lltools.zoomTo(srcCrs, lat, lon) else: if self.iface.openFeatureForm(layer, feat): layer.addFeature(feat) self.lltools.zoomTo(srcCrs, lat, lon)
def testEncodeValue(self): """ test encoding various values for use in GeoJSON strings """ self.assertEqual(QgsJsonUtils.encodeValue(NULL), 'null') self.assertEqual(QgsJsonUtils.encodeValue(5), '5') self.assertEqual(QgsJsonUtils.encodeValue(5.9), '5.9') self.assertEqual(QgsJsonUtils.encodeValue(5999999999), '5999999999') self.assertEqual(QgsJsonUtils.encodeValue('string'), '"string"') self.assertEqual(QgsJsonUtils.encodeValue('str\ning'), '"str\\ning"') self.assertEqual(QgsJsonUtils.encodeValue('str\ring'), '"str\\ring"') self.assertEqual(QgsJsonUtils.encodeValue('str\bing'), '"str\\bing"') self.assertEqual(QgsJsonUtils.encodeValue('str\ting'), '"str\\ting"') self.assertEqual(QgsJsonUtils.encodeValue('str\\ing'), '"str\\\\ing"') self.assertEqual(QgsJsonUtils.encodeValue('str\\ning'), '"str\\\\ning"') self.assertEqual(QgsJsonUtils.encodeValue('str\n\\\\ing'), '"str\\n\\\\\\\\ing"') self.assertEqual(QgsJsonUtils.encodeValue('str/ing'), '"str\\/ing"') self.assertEqual(QgsJsonUtils.encodeValue([5, 6]), '[5,6]') self.assertEqual(QgsJsonUtils.encodeValue(['a', 'b', 'c']), '["a","b","c"]') self.assertEqual(QgsJsonUtils.encodeValue(['a', 3, 'c']), '["a",3,"c"]') self.assertEqual(QgsJsonUtils.encodeValue(['a', 'c\nd']), '["a","c\\nd"]') # handle differences due to Qt5 version, where compact output now lacks \n enc_str = QgsJsonUtils.encodeValue({'key': 'value', 'key2': 5}) self.assertTrue(enc_str == '{"key":"value",\n"key2":5}' or enc_str == '{"key":"value","key2":5}') enc_str = QgsJsonUtils.encodeValue({'key': [1, 2, 3], 'key2': {'nested': 'nested\\result'}}) self.assertTrue(enc_str == '{"key":[1,2,3],\n"key2":{"nested":"nested\\\\result"}}' or enc_str == '{"key":[1,2,3],"key2":{"nested":"nested\\\\result"}}')
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)
def get_feature_with_form_scope(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None: """ Get filtered features with a form scope In parameters: LAYER=wms-layer-name FILTER=An expression to filter layer FORM_FEATURE={"type": "Feature", "geometry": {}, "properties": {}} // optionals FIELDS=list of requested field separated by comma WITH_GEOMETRY=False """ layer_name = params.get('LAYER', '') if not layer_name: raise ExpressionServiceError( "Bad request error", "Invalid 'GetFeatureWithFormScope' REQUEST: LAYER parameter is mandatory", 400) # get layer layer = find_vector_layer(layer_name, project) # layer not found if not layer: raise ExpressionServiceError( "Bad request error", "Invalid LAYER parameter for 'VirtualField': {} provided".format(layer_name), 400) # get filter exp_filter = params.get('FILTER', '') if not exp_filter: raise ExpressionServiceError( "Bad request error", "Invalid 'GetFeatureWithFormScope' REQUEST: FILTER parameter is mandatory", 400) # get form feature form_feature = params.get('FORM_FEATURE', '') if not form_feature: raise ExpressionServiceError( "Bad request error", "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE parameter is mandatory", 400) # Check features try: geojson = json.loads(form_feature) except Exception: QgsMessageLog.logMessage( "JSON loads form feature '{}' exception:\n{}".format(form_feature, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed".format(form_feature), 400) if not geojson or not isinstance(geojson, dict): raise ExpressionServiceError( "Bad request error", "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed".format(form_feature), 400) if ('type' not in geojson) or geojson['type'] != 'Feature': raise ExpressionServiceError( "Bad request error", ("Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed: type not defined " "or not Feature.").format(form_feature), 400) # try to load form feature # read fields form_feature_fields = QgsJsonUtils.stringToFields( form_feature, QTextCodec.codecForName("UTF-8")) # read features form_feature_list = QgsJsonUtils.stringToFeatureList( form_feature, form_feature_fields, QTextCodec.codecForName("UTF-8")) # features not well formed if not form_feature_list: raise ExpressionServiceError( "Bad request error", ("Invalid FORM_FEATURE for 'GetFeatureWithFormScope': not GeoJSON feature provided\n" "{}").format(form_feature), 400) if len(form_feature_list) != 1: raise ExpressionServiceError( "Bad request error", ("Invalid FORM_FEATURE for 'GetFeatureWithFormScope': not GeoJSON feature provided\n" "{}").format(form_feature), 400) # Get the form feature form_feat = form_feature_list[0] # create expression context exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope(project)) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) exp_context.appendScope(QgsExpressionContextUtils.formScope(form_feat)) # create distance area context da = QgsDistanceArea() da.setSourceCrs(layer.crs(), project.transformContext()) da.setEllipsoid(project.ellipsoid()) # Get filter expression exp_f = QgsExpression(exp_filter) exp_f.setGeomCalculator(da) exp_f.setDistanceUnits(project.distanceUnits()) exp_f.setAreaUnits(project.areaUnits()) if exp_f.hasParserError(): raise ExpressionServiceError( "Bad request error", "Invalid FILTER for 'GetFeatureWithFormScope': Error \"{}\": {}".format( exp_filter, exp_f.parserErrorString()), 400) if not exp_f.isValid(): raise ExpressionServiceError( "Bad request error", "Invalid FILTER for 'GetFeatureWithFormScope': Expression not valid \"{}\"".format(exp_filter), 400) exp_f.prepare(exp_context) req = QgsFeatureRequest(exp_f, exp_context) # With geometry with_geom = params.get('WITH_GEOMETRY', '').lower() in ['true', '1', 't'] if not with_geom: req.setFlags(QgsFeatureRequest.NoGeometry) # Fields pk_attributes = layer.primaryKeyAttributes() attribute_list = [i for i in pk_attributes] fields = layer.fields() r_fields = [f.strip() for f in params.get('FIELDS', '').split(',') if f] for f in r_fields: attribute_list.append(fields.indexOf(f)) # response response.setStatusCode(200) response.setHeader("Content-Type", "application/json") response.write('{ "type": "FeatureCollection","features":[') response.flush() json_exporter = QgsJsonExporter(layer) if attribute_list: json_exporter.setAttributes(attribute_list) separator = '' for feat in layer.getFeatures(req): fid = layer_name + '.' + get_server_fid(feat, pk_attributes) response.write(separator + json_exporter.exportFeature(feat, {}, fid)) response.flush() separator = ',\n' response.write(']}') return
def virtualFields(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None: """ Get virtual fields for features In parameters: LAYER=wms-layer-name VIRTUALS={"key1": "first expression", "key2": "second expression"} // optionals FILTER=An expression to filter layer FIELDS=list of requested field separated by comma WITH_GEOMETRY=False """ layer_name = params.get('LAYER', '') if not layer_name: raise ExpressionServiceError( "Bad request error", "Invalid 'VirtualFields' REQUEST: LAYER parameter is mandatory", 400) # get layer layer = find_vector_layer(layer_name, project) # layer not found if not layer: raise ExpressionServiceError( "Bad request error", "Invalid LAYER parameter for 'VirtualFields': {} provided".format(layer_name), 400) # get virtuals virtuals = params.get('VIRTUALS', '') if not virtuals: raise ExpressionServiceError( "Bad request error", "Invalid 'VirtualFields' REQUEST: VIRTUALS parameter is mandatory", 400) # try to load virtuals dict try: vir_json = json.loads(virtuals) except Exception: QgsMessageLog.logMessage( "JSON loads virtuals '{}' exception:\n{}".format(virtuals, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'VirtualFields' REQUEST: VIRTUALS '{}' are not well formed".format(virtuals), 400) if not isinstance(vir_json, dict): raise ExpressionServiceError( "Bad request error", "Invalid 'VirtualFields' REQUEST: VIRTUALS '{}' are not well formed".format(virtuals), 400) # create expression context exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope(project)) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) # create distance area context da = QgsDistanceArea() da.setSourceCrs(layer.crs(), project.transformContext()) da.setEllipsoid(project.ellipsoid()) # parse virtuals exp_map = {} exp_parser_errors = [] for k, e in vir_json.items(): exp = QgsExpression(e) exp.setGeomCalculator(da) exp.setDistanceUnits(project.distanceUnits()) exp.setAreaUnits(project.areaUnits()) if exp.hasParserError(): exp_parser_errors.append('Error "{}": {}'.format(e, exp.parserErrorString())) continue if not exp.isValid(): exp_parser_errors.append('Expression not valid "{}"'.format(e)) continue exp.prepare(exp_context) exp_map[k] = exp # expression parser errors found if exp_parser_errors: raise ExpressionServiceError( "Bad request error", "Invalid VIRTUALS for 'VirtualFields':\n{}".format('\n'.join(exp_parser_errors)), 400) req = QgsFeatureRequest() # get filter req_filter = params.get('FILTER', '') if req_filter: req_exp = QgsExpression(req_filter) req_exp.setGeomCalculator(da) req_exp.setDistanceUnits(project.distanceUnits()) req_exp.setAreaUnits(project.areaUnits()) if req_exp.hasParserError(): raise ExpressionServiceError( "Bad request error", "Invalid FILTER for 'VirtualFields' Error \"{}\": {}".format( req_filter, req_exp.parserErrorString()), 400) if not req_exp.isValid(): raise ExpressionServiceError( "Bad request error", "Invalid FILTER for 'VirtualFields' Expression not valid \"{}\"".format(req_filter), 400) req_exp.prepare(exp_context) req = QgsFeatureRequest(req_exp, exp_context) # With geometry with_geom = params.get('WITH_GEOMETRY', '').lower() in ['true', '1', 't'] if not with_geom: req.setFlags(QgsFeatureRequest.NoGeometry) # Fields pk_attributes = layer.primaryKeyAttributes() attribute_list = [i for i in pk_attributes] fields = layer.fields() r_fields = [f.strip() for f in params.get('FIELDS', '').split(',') if f] for f in r_fields: attribute_list.append(fields.indexOf(f)) # response response.setStatusCode(200) response.setHeader("Content-Type", "application/json") response.write('{ "type": "FeatureCollection","features":[') response.flush() json_exporter = QgsJsonExporter(layer) if attribute_list: json_exporter.setAttributes(attribute_list) separator = '' for feat in layer.getFeatures(req): fid = layer_name + '.' + get_server_fid(feat, pk_attributes) extra = {} # Update context exp_context.setFeature(feat) exp_context.setFields(feat.fields()) # Evaluate expressions for virtual fields errors = {} for k, exp in exp_map.items(): value = exp.evaluate(exp_context) if exp.hasEvalError(): extra[k] = None errors[k] = exp.evalErrorString() else: extra[k] = json.loads(QgsJsonUtils.encodeValue(value)) errors[k] = exp.expression() response.write(separator + json_exporter.exportFeature(feat, extra, fid)) response.flush() separator = ',\n' response.write(']}') return
def evaluate(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None: """ Evaluate expressions against layer or features In parameters: LAYER=wms-layer-name EXPRESSION=An expression to evaluate or EXPRESSIONS=["first expression", "second expression"] or EXPRESSIONS={"key1": "first expression", "key2": "second expression"} // optionals FEATURE={"type": "Feature", "geometry": {}, "properties": {}} or FEATURES=[{"type": "Feature", "geometry": {}, "properties": {}}, {"type": "Feature", "geometry": {}, "properties": {}}] FORM_SCOPE=boolean to add formScope based on provided features """ layername = params.get('LAYER', '') if not layername: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: LAYER parameter is mandatory", 400) # get layer layer = find_vector_layer(layername, project) # layer not found if not layer: raise ExpressionServiceError( "Bad request error", "Invalid LAYER parameter for 'Evaluate': {} provided".format(layername), 400) # get expressions expressions = params.get('EXPRESSIONS', '') if not expressions: expression = params.get('EXPRESSION', '') if not expression: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: EXPRESSION or EXPRESSIONS parameter is mandatory", 400) expressions = '["{}"]'.format(expression) # try to load expressions list or dict try: exp_json = json.loads(expressions) except Exception: QgsMessageLog.logMessage( "JSON loads expressions '{}' exception:\n{}".format(expressions, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: EXPRESSIONS '{}' are not well formed".format(expressions), 400) # create expression context exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope(project)) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) # create distance area context da = QgsDistanceArea() da.setSourceCrs(layer.crs(), project.transformContext()) da.setEllipsoid(project.ellipsoid()) # parse expressions exp_map = {} exp_parser_errors = [] exp_items = [] if isinstance(exp_json, list): exp_items = enumerate(exp_json) elif isinstance(exp_json, dict): exp_items = exp_json.items() for k, e in exp_items: exp = QgsExpression(e) exp.setGeomCalculator(da) exp.setDistanceUnits(project.distanceUnits()) exp.setAreaUnits(project.areaUnits()) if exp.hasParserError(): exp_parser_errors.append('Error "{}": {}'.format(e, exp.parserErrorString())) continue if not exp.isValid(): exp_parser_errors.append('Expression not valid "{}"'.format(e)) continue exp.prepare(exp_context) exp_map[k] = exp # expression parser errors found if exp_parser_errors: raise ExpressionServiceError( "Bad request error", "Invalid EXPRESSIONS for 'Evaluate':\n{}".format('\n'.join(exp_parser_errors)), 400) # get features features = params.get('FEATURES', '') if not features: feature = params.get('FEATURE', '') if feature: features = '[' + feature + ']' # create the body body = { 'status': 'success', 'results': [], 'errors': [], 'features': 0 } # without features just evaluate expression with layer context if not features: result = {} error = {} for k, exp in exp_map.items(): value = exp.evaluate(exp_context) if exp.hasEvalError(): result[k] = None error[k] = exp.evalErrorString() else: result[k] = json.loads(QgsJsonUtils.encodeValue(value)) body['results'].append(result) body['errors'].append(error) write_json_response(body, response) return # Check features try: geojson = json.loads(features) except Exception: QgsMessageLog.logMessage( "JSON loads features '{}' exception:\n{}".format(features, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if not geojson or not isinstance(geojson, list) or len(geojson) == 0: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if 'type' not in geojson[0] or geojson[0]['type'] != 'Feature': raise ExpressionServiceError( "Bad request error", ("Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed: type not defined or not " "Feature.").format(features), 400) # try to load features # read fields feature_fields = QgsJsonUtils.stringToFields( '{ "type": "FeatureCollection","features":' + features + '}', QTextCodec.codecForName("UTF-8")) # read features feature_list = QgsJsonUtils.stringToFeatureList( '{ "type": "FeatureCollection","features":' + features + '}', feature_fields, QTextCodec.codecForName("UTF-8")) # features not well formed if not feature_list: raise ExpressionServiceError( "Bad request error", "Invalid FEATURES for 'Evaluate': not GeoJSON features array provided\n{}".format(features), 400) # Extend layer fields with this provided in GeoJSON Features feat_fields = QgsFields(layer.fields()) feat_fields.extend(feature_fields) # form scope add_form_scope = params.get('FORM_SCOPE', '').lower() in ['true', '1', 't'] # loop through provided features to evaluate expressions for f in feature_list: # clone the features with all attributes # those defined in layer + fields from GeoJSON Features feat = QgsFeature(feat_fields) feat.setGeometry(f.geometry()) for field in f.fields(): fname = field.name() if feat_fields.indexOf(fname) != -1: feat.setAttribute(fname, f[fname]) # Add form scope to expression context if add_form_scope: exp_context.appendScope(QgsExpressionContextUtils.formScope(feat)) exp_context.setFeature(feat) exp_context.setFields(feat.fields()) # Evaluate expressions with the new feature result = {} error = {} for k, exp in exp_map.items(): if add_form_scope: # need to prepare the expression because the context has been updated with a new scope exp.prepare(exp_context) value = exp.evaluate(exp_context) if exp.hasEvalError(): result[k] = None error[k] = exp.evalErrorString() else: result[k] = json.loads(QgsJsonUtils.encodeValue(value)) error[k] = exp.expression() body['results'].append(result) body['errors'].append(error) write_json_response(body, response) return
def replace_expression_text(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None: """ Replace expression texts against layer or features In parameters: LAYER=wms-layer-name STRING=A string with expression between [% and %] or STRINGS=["first string with expression", "second string with expression"] or STRINGS={"key1": "first string with expression", "key2": "second string with expression"} // optionals FEATURE={"type": "Feature", "geometry": {}, "properties": {}} or FEATURES=[{"type": "Feature", "geometry": {}, "properties": {}}, {"type": "Feature", "geometry": {}, "properties": {}}] FORM_SCOPE=boolean to add formScope based on provided features """ layername = params.get('LAYER', '') if not layername: raise ExpressionServiceError( "Bad request error", "Invalid 'ReplaceExpressionText' REQUEST: LAYER parameter is mandatory", 400) # get layer layer = find_vector_layer(layername, project) # layer not found if not layer: raise ExpressionServiceError( "Bad request error", "Invalid LAYER parameter for 'ReplaceExpressionText': {} provided".format(layername), 400) # get strings strings = params.get('STRINGS', '') if not strings: the_string = params.get('STRING', '') if not the_string: raise ExpressionServiceError( "Bad request error", "Invalid 'ReplaceExpressionText' REQUEST: STRING or STRINGS parameter is mandatory", 400) strings = '["{}"]'.format(the_string) # try to load expressions list or dict try: str_json = json.loads(strings) except Exception: QgsMessageLog.logMessage( "JSON loads strings '{}' exception:\n{}".format(strings, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'ReplaceExpressionText' REQUEST: STRINGS '{}' are not well formed".format(strings), 400) # get features features = params.get('FEATURES', '') if not features: feature = params.get('FEATURE', '') if feature: features = '[' + feature + ']' # create expression context exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope(project)) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) # create distance area context da = QgsDistanceArea() da.setSourceCrs(layer.crs(), project.transformContext()) da.setEllipsoid(project.ellipsoid()) # organized strings str_map = {} str_items = [] if isinstance(str_json, list): str_items = enumerate(str_json) elif isinstance(str_json, dict): str_items = str_json.items() for k, s in str_items: str_map[k] = s # create the body body = { 'status': 'success', 'results': [], 'errors': [], 'features': 0 } # without features just replace expression string with layer context if not features: result = {} for k, s in str_map.items(): value = QgsExpression.replaceExpressionText(s, exp_context, da) result[k] = json.loads(QgsJsonUtils.encodeValue(value)) body['results'].append(result) write_json_response(body, response) return # Check features try: geojson = json.loads(features) except Exception: QgsMessageLog.logMessage( "JSON loads features '{}' exception:\n{}".format(features, traceback.format_exc()), "lizmap", Qgis.Critical) raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if not geojson or not isinstance(geojson, list) or len(geojson) == 0: raise ExpressionServiceError( "Bad request error", "Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed".format(features), 400) if ('type' not in geojson[0]) or geojson[0]['type'] != 'Feature': raise ExpressionServiceError( "Bad request error", ("Invalid 'Evaluate' REQUEST: FEATURES '{}' are not well formed: type not defined or not " "Feature.").format(features), 400) # try to load features # read fields feature_fields = QgsJsonUtils.stringToFields( '{ "type": "FeatureCollection","features":' + features + '}', QTextCodec.codecForName("UTF-8")) # read features feature_list = QgsJsonUtils.stringToFeatureList( '{ "type": "FeatureCollection","features":' + features + '}', feature_fields, QTextCodec.codecForName("UTF-8")) # features not well formed if not feature_list: raise ExpressionServiceError( "Bad request error", ("Invalid FEATURES for 'ReplaceExpressionText': not GeoJSON features array " "provided\n{}").format(features), 400) # Extend layer fields with this provided in GeoJSON Features feat_fields = QgsFields(layer.fields()) feat_fields.extend(feature_fields) # form scope add_form_scope = params.get('FORM_SCOPE', '').lower() in ['true', '1', 't'] # loop through provided features to replace expression strings for f in feature_list: # clone the features with all attributes # those defined in layer + fields from GeoJSON Features feat = QgsFeature(feat_fields) feat.setGeometry(f.geometry()) for field in f.fields(): field_name = field.name() if feat_fields.indexOf(field_name) != -1: feat.setAttribute(field_name, f[field_name]) # Add form scope to expression context if add_form_scope: exp_context.appendScope(QgsExpressionContextUtils.formScope(feat)) exp_context.setFeature(feat) exp_context.setFields(feat.fields()) # replace expression strings with the new feature result = {} for k, s in str_map.items(): value = QgsExpression.replaceExpressionText(s, exp_context, da) result[k] = json.loads(QgsJsonUtils.encodeValue(value)) body['results'].append(result) write_json_response(body, response) return
def add_geojson_features(geojson, project, qgis_layer_id=None, connection_id=None, new_layer_name=None, name=None, style=None): """Add geojson features to a destination layer, the layer can be specified by passing a QgsVectorLayer instance or by specifying a connection and a new layer name plus the QDjango project for the new layer. The connection may assume the special values `__shapefile__`, `__spatialite__` or `__geopackage__` for the creation of new OGR files of the corresponding type. The creation of the new layer may raise an exception is a layer with the same name as new_layer_name already exists. For already existing layers the `qgis_layer_id` can be used, provided that the layer belongs to the current `project`. Returns the qgis_layer_id :param geojson: new features in GeoJSON format :type geojson: str :param project: QDjango Project instance for the new or the existing layer :type project: Project instance :param layer_id: optional, QGIS layer id :type layer_id: QGIS layer id :param connection: optional, connection id or the special value `__shapefile__`, `__spatialite__` or `__geopackage__` :type connection: str :param new_layer_name: optional, name of the new layer :type new_layer_name: str :param name: optional, name of the isochrone, default to current datetime :type name: str :param style: optional, dictionary with style properties: example {'color': [100, 50, 123], 'transparency': 0.5, 'stroke_width: 3 } :type style: dict :raises Exception: raise on error :rtype: str """ # Additional fields that are not returned by the service as feature attributes json_data = json.loads(geojson) if name is None: name = "Isochrone %s" % QDateTime.currentDateTime().toString( Qt.ISODateWithMs) metadata = { 'range_type': json_data['metadata']['query']['range_type'], 'name': name, # 'timestamp': json_data['metadata']['timestamp'], // Not supported } for f in json_data['features']: f['properties'].update(metadata) geojson = json.dumps(json_data) fields = QgsJsonUtils.stringToFields(geojson) # Patch timestamp type to DateTime // Not supported # fields.remove(fields.lookupField('timestamp')) #fields.append(QgsField('timestamp', QVariant.DateTime)) # Create the new layer if connection_id is not None: def _write_to_ogr(destination_path, new_layer_name, driverName=None): """Writes features to new or existing OGR layer""" tmp_dir = QTemporaryDir() tmp_path = os.path.join(tmp_dir.path(), 'isochrone.json') with open(tmp_path, 'w+') as f: f.write(geojson) tmp_layer = QgsVectorLayer(tmp_path, 'tmp_isochrone', 'ogr') if not tmp_layer.isValid(): raise Exception( _('Cannot create temporary layer for isochrone result.')) # Note: shp attribute names are max 10 chars long save_options = QgsVectorFileWriter.SaveVectorOptions() if driverName is not None: save_options.driverName = driverName save_options.layerName = new_layer_name save_options.fileEncoding = 'utf-8' # This is nonsense to me: if the file does not exist the actionOnExistingFile # should be ignored instead of raising an error, probable QGIS bug if os.path.exists(destination_path): # Check if the layer already exists layer_exists = QgsVectorFileWriter.targetLayerExists( destination_path, new_layer_name) if layer_exists: raise Exception( _('Cannot save isochrone result to destination layer: layer already exists (use "qgis_layer_id" instead)!' )) save_options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer error_code, error_message = QgsVectorFileWriter.writeAsVectorFormatV2( tmp_layer, destination_path, project.qgis_project.transformContext(), save_options) if error_code != QgsVectorFileWriter.NoError: raise Exception( _('Cannot save isochrone result to destination layer: ') + error_message) layer_uri = destination_path if driverName != 'ESRI Shapefile': layer_uri += '|layername=' + new_layer_name provider = 'ogr' return layer_uri, provider destination_path = None if connection_id == '__shapefile__': # new shapefile driverName = 'ESRI Shapefile' extension = 'shp' elif connection_id == '__spatialite__': # new sqlite driverName = 'SpatiaLite' extension = 'sqlite' elif connection_id == '__geopackage__': # new gpkg driverName = 'GPKG' extension = 'gpkg' else: # Add new table to an existing DB connection try: connection = get_db_connections( project.qgis_project)[connection_id] except: raise Exception(_('Wrong connection id.')) if connection['provider'] == 'ogr': destination_path = connection_id driverName = 'GPKG' if destination_path.lower().endswith( '.gpkg') else 'SpatiaLite' else: driverName = None # Create a new file/layer if driverName is not None: new_layer_name = os.path.basename(new_layer_name) if destination_path is None: # new files! destination_path = os.path.join( settings.DATASOURCE_PATH, "{}.{}".format(new_layer_name, extension)) i = 0 while os.path.exists(destination_path): i += 1 destination_path = os.path.join( settings.DATASOURCE_PATH, "{}_{}.{}".format(new_layer_name, i, extension)) layer_uri, provider = _write_to_ogr(destination_path, new_layer_name, driverName) # Create a new DB table else: assert connection['provider'] != 'ogr' md = QgsProviderRegistry.instance().providerMetadata( connection['provider']) if not md: raise Exception( _('Error creating destination layer connection.')) conn = md.createConnection(connection_id, {}) try: conn.createVectorTable(connection['schema'], new_layer_name, fields, QgsWkbTypes.Polygon, QgsCoordinateReferenceSystem(4326), False, {'geometryColumn': 'geom'}) except QgsProviderConnectionException as ex: raise Exception( _('Error creating destination layer: ') + str(ex)) uri = QgsDataSourceUri(conn.uri()) uri.setTable(new_layer_name) uri.setSchema(connection['schema']) uri.setSrid('4326') uri.setGeometryColumn('geom') provider = connection['provider'] layer_uri = uri.uri() # Now reload the new layer and add it to the project qgis_layer = QgsVectorLayer(layer_uri, new_layer_name, provider) if not qgis_layer.isValid(): raise Exception( _('Error creating destination layer: layer is not valid!')) qgis_layer_id = qgis_layer.id() with QgisProjectFileLocker(project) as project: apply_style(qgis_layer, style, True, name) project.qgis_project.addMapLayers([qgis_layer]) root = project.qgis_project.layerTreeRoot() if qgis_layer_id not in root.findLayerIds(): # Append layer at the end root.insertLayer(-1, qgis_layer) if not project.update_qgis_project(): raise Exception( _('Error saving the destination layer: could not write project!' )) # Retrieve the layer again because saving the project deleted it qgis_layer = project.qgis_project.mapLayer(qgis_layer_id) # Create Layer object instance, created = Layer.objects.get_or_create( qgs_layer_id=qgis_layer_id, project=project, defaults={ 'origname': new_layer_name, 'name': new_layer_name, 'title': new_layer_name, 'is_visible': True, 'layer_type': provider, 'srid': 4326, 'datasource': layer_uri, 'geometrytype': 'Polygon', # TODO: make this a property of the Layer object 'database_columns': str([{ 'name': f.name(), 'type': QVariant.typeToName(f.type()).upper(), 'label': f.displayName() } for f in qgis_layer.fields()]), }) if not created: raise Exception( _('Error adding destination Layer to the project: layer already exists.' )) # for OGR (already filled with features) returns the id of the new layer if driverName is not None: return qgis_layer_id # Append to an existing layer qgis_layer = project.qgis_project.mapLayer(qgis_layer_id) if qgis_layer is None: raise Exception( _('Error opening destination layer %s: layer not found in QGIS project!' % qgis_layer_id)) features = QgsJsonUtils.stringToFeatureList(geojson, fields) compatible_features = [] for f in features: compatible_features.extend( QgsVectorLayerUtils.makeFeatureCompatible(f, qgis_layer)) if qgis_layer.crs().isValid( ) and qgis_layer.crs() != QgsCoordinateReferenceSystem(4326): ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem(4326), qgis_layer.crs(), project.qgis_project) for f in compatible_features: geom = f.geometry() if geom.transform(ct) != QgsGeometry.Success: raise Exception( _('Error transforming geometry from 4326 to destination layer CRS.' )) f.setGeometry(geom) if len(compatible_features) == 0: raise Exception(_('No compatible features returned from isochrone.')) if not qgis_layer.startEditing(): raise Exception(_('Destination layer is not editable.')) # Add features to the layer if not qgis_layer.addFeatures(compatible_features): raise Exception(_('Error adding features to the destination layer.')) if not qgis_layer.commitChanges(): raise Exception( _('Error committing features to the destination layer.')) if style is not None: with QgisProjectFileLocker(project) as project: apply_style(qgis_layer, style, False, name) project.update_qgis_project() return qgis_layer_id
def handle_add_layer(self): """Create a new layer by name (rev_lyr)""" slds = self.get_sld() selected_name = self.dlg.mComboBox.currentText() selected_id = self.dataset_dictionary[selected_name] # Group name equals selected dataset name group = self.create_group(selected_name) # Get metadata and features from NgisOpenAPI try: metadata_from_api = self.client.getDatasetMetadata(selected_id) epsg = metadata_from_api.crs_epsg features_from_api = self.client.getDatasetFeatures( metadata_from_api.id, metadata_from_api.bbox, epsg) except Exception as e: error = ApiError("Nedlasting av data mislyktes", "Kunne ikke laste ned datasett", e) self.iface.messageBar().pushMessage(error.title, error.detail, error.show_more, level=2, duration=10) return crs_from_api = features_from_api['crs']['properties']['name'] features_by_type = {} # Extract features from GeoJSON into dictionary for feature in features_from_api['features']: feature_type = feature['properties']['featuretype'] features_by_type.setdefault(feature_type, []).append(feature) features_from_api['features'] = None for feature_type, features_list in features_by_type.items(): # Create a new GeoJSON object containing a single featuretype features_dict = features_from_api.copy() features_dict['features'] = features_list features_json = json.dumps(features_dict, ensure_ascii=False) # Identify fields and features from GeoJSON codec = QTextCodec.codecForName("UTF-8") fields = QgsJsonUtils.stringToFields(features_json, codec) newFeatures = QgsJsonUtils.stringToFeatureList( features_json, fields, codec) # If different geometry types are identified, separate them into individual layers geometry_dict = {} if newFeatures: for feature in newFeatures: featuretype = feature.attribute('featuretype') geom_type = feature.geometry() geom_type = QgsWkbTypes.displayString( int(geom_type.wkbType())) if geom_type not in geometry_dict: geometry_dict[geom_type] = {} if featuretype not in geometry_dict[geom_type]: geometry_dict[geom_type][featuretype] = [] geometry_dict[geom_type][featuretype].append(feature) for geom_type, feature_types in geometry_dict.items(): for feature_type, features in feature_types.items(): lyr = QgsVectorLayer(f'{geom_type}?crs={crs_from_api}', f'{feature_type}-{geom_type}', "memory") #lyr = QgsVectorLayer(f'{geom_type}?crs=EPSG:25832', f'{feature_type}-{geom_type}', "memory") #TODO Remove QgsProject.instance().addMapLayer(lyr, False) lyr.startEditing() add_fields_to_layer(lyr, fields, feature_type) lyr.commitChanges() l_d = lyr.dataProvider() l_d.addFeatures(features) # update the extent of rev_lyr lyr.updateExtents() # save changes made in 'rev_lyr' lyr.commitChanges() group.addLayer(lyr) #lyr.committedFeaturesAdded.connect(self.handleCommitedAddedFeatures) #lyr.committedFeaturesRemoved.connect(self.handleCommittedFeaturesRemoved) #lyr.featuresDeleted.connect(self.handleDeletedFeatures) #lyr.committedGeometriesChanges(self.ee) lyr.beforeCommitChanges.connect( self.handle_before_commitchanges) if feature_type in slds: lyr.loadSldStyle(slds[feature_type]) self.dataset_dictionary[lyr.id()] = selected_id self.feature_type_dictionary[lyr.id()] = feature_type
def convertCoordinate(self, text): try: if self.settings.zoomToProjIsMGRS(): # An MGRS coordinate only format has been specified. This will result in an exception # if it is not a valid MGRS coordinate text2 = re.sub(r'\s+', '', str(text)) # Remove all white space lat, lon = mgrs.toWgs(text2) return (lat, lon, epsg4326) if self.settings.zoomToProjIsPlusCodes(): # A Plus Codes coordinate has been selected. This will result in an exception # if it is not a valid plus codes coordinate. coord = olc.decode(text) lat = coord.latitudeCenter lon = coord.longitudeCenter return (lat, lon, epsg4326) if self.settings.zoomToProjIsStandardUtm(): # A Standard UTM coordinate has been selected. This will result in an exception # if it is not a valid utm coordinate. pt = utmString2Crs(text) return (pt.y(), pt.x(), epsg4326) if self.settings.zoomToProjIsGeohash(): # A Geohash coordinate has been selected. This will result in an exception # if it is not a valid Geohash coordinate. (lat, lon) = geohash.decode(text) return (float(lat), float(lon), epsg4326) # Check for other formats if text[0] == '{': # This may be a GeoJSON point codec = QTextCodec.codecForName("UTF-8") fields = QgsJsonUtils.stringToFields(text, codec) fet = QgsJsonUtils.stringToFeatureList(text, fields, codec) if (len(fet) == 0) or not fet[0].isValid(): raise ValueError('Invalid Coordinates') geom = fet[0].geometry() if geom.isEmpty() or (geom.wkbType() != QgsWkbTypes.Point): raise ValueError('Invalid GeoJSON Geometry') pt = geom.asPoint() return (pt.y(), pt.x(), epsg4326) # Check to see if it is standard UTM if isUtm(text): pt = utmString2Crs(text) return (pt.y(), pt.x(), epsg4326) # Check to see if it is an MGRS coordinate try: text2 = re.sub(r'\s+', '', str(text)) lat, lon = mgrs.toWgs(text2) return (lat, lon, epsg4326) except Exception: pass # Check to see if it is a plus codes string try: coord = olc.decode(text) lat = coord.latitudeCenter lon = coord.longitudeCenter return (lat, lon, epsg4326) except Exception: pass # Check to see if it is a WKT POINT format if re.search(r'POINT\(', text) is not None: m = re.findall( r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) if self.settings.zoomToProjIsWgs84(): srcCrs = epsg4326 elif self.settings.zoomToProjIsProjectCRS(): srcCrs = self.canvas.mapSettings().destinationCrs() else: srcCrs = self.settings.zoomToCustomCRS() return (lat, lon, srcCrs) # We are left with either DMS or decimal degrees in one of the projections if self.settings.zoomToProjIsWgs84(): lat, lon = parseDMSString(text, self.settings.zoomToCoordOrder) return (lat, lon, epsg4326) # We are left with a non WGS 84 decimal projection coords = re.split(r'[\s,;:]+', text, 1) if len(coords) < 2: raise ValueError('Invalid Coordinates') if self.settings.zoomToCoordOrder == self.settings.OrderYX: lat = float(coords[0]) lon = float(coords[1]) else: lon = float(coords[0]) lat = float(coords[1]) if self.settings.zoomToProjIsProjectCRS(): srcCrs = self.canvas.mapSettings().destinationCrs() else: srcCrs = self.settings.zoomToCustomCRS() return (lat, lon, srcCrs) except Exception: raise ValueError('Invalid Coordinates')
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 save_vector_data(self, metadata_layer, post_layer_data, has_transactions, post_save_signal=True, **kwargs): """Save vector editing data :param metadata_layer: metadata of the layer being edited :type metadata_layer: MetadataVectorLayer :param post_layer_data: post data with 'add', 'delete' etc. :type post_layer_data: dict :param has_transactions: true if the layer support transactions :type has_transactions: bool :param post_save_signal: if this is a post_save_signal call, defaults to True :type post_save_signal: bool, optional """ # Check atomic capabilities for validation # ----------------------------------------------- #for mode_editing in (EDITING_POST_DATA_ADDED, EDITING_POST_DATA_UPDATED, EDITING_POST_DATA_DELETED): # try to get layer model object from metatada_layer layer = getattr(metadata_layer, 'layer', self.layer) if EDITING_POST_DATA_ADDED in post_layer_data and len( post_layer_data[EDITING_POST_DATA_ADDED]) > 0: if not self.request.user.has_perm('qdjango.add_feature', layer): raise ValidationError( _('Sorry but your user doesn\'t has \'Add Feature\' capability' )) if EDITING_POST_DATA_DELETED in post_layer_data and len( post_layer_data[EDITING_POST_DATA_DELETED]) > 0: if not self.request.user.has_perm('qdjango.delete_feature', layer): raise ValidationError( _('Sorry but your user doesn\'t has \'Delete Feature\' capability' )) if EDITING_POST_DATA_UPDATED in post_layer_data and len( post_layer_data[EDITING_POST_DATA_UPDATED]) > 0: if not self.request.user.has_perm('qdjango.change_feature', layer) and \ not self.request.user.has_perm('qdjango.change_attr_feature', layer): raise ValidationError( _('Sorry but your user doesn\'t has \'Change or Change Attributes Features\' capability' )) # get initial featurelocked metadata_layer.lock.getInitialFeatureLockedIds() # get lockids from client metadata_layer.lock.setLockeFeaturesFromClient( post_layer_data['lockids']) # data for response insert_ids = list() lock_ids = list() # FIXME: check this out # for add check if is a metadata_layer and referenced field is a pk is_referenced_field_is_pk = 'referenced_layer_insert_ids' in kwargs and kwargs['referenced_layer_insert_ids'] \ and hasattr(metadata_layer, 'referenced_field_is_pk') \ and metadata_layer.referenced_field_is_pk # Get the layer qgis_layer = metadata_layer.qgis_layer for mode_editing in (EDITING_POST_DATA_ADDED, EDITING_POST_DATA_UPDATED): if mode_editing in post_layer_data: for geojson_feature in post_layer_data[mode_editing]: data_extra_fields = {'feature': geojson_feature} # Clear any old error qgis_layer.dataProvider().clearErrors() # add media data self.add_media_property(geojson_feature, metadata_layer) # for GEOSGeometry of Django 2.2 it must add crs to feature if is not set if a geo feature if metadata_layer.geometry_type != QGIS_LAYER_TYPE_NO_GEOM: if geojson_feature[ 'geometry'] and 'crs' not in geojson_feature[ 'geometry']: geojson_feature['geometry'][ 'crs'] = "{}:{}".format( self.layer.project.group.srid.auth_name, self.layer.project.group.srid.auth_srid) # reproject data if necessary if kwargs[ 'reproject'] and metadata_layer.geometry_type != QGIS_LAYER_TYPE_NO_GEOM: self.reproject_feature(geojson_feature, to_layer=True) # case relation data ADD, if father referenced field is pk if is_referenced_field_is_pk: for newid in kwargs['referenced_layer_insert_ids']: if geojson_feature['properties'][ metadata_layer. referencing_field] == newid['clientid']: geojson_feature['properties'][ metadata_layer. referencing_field] = newid['id'] if mode_editing == EDITING_POST_DATA_UPDATED: # control feature locked if not metadata_layer.lock.checkFeatureLocked( geojson_feature['id']): raise Exception( self.no_more_lock_feature_msg.format( geojson_feature['id'], metadata_layer.client_var)) # Send for validation # Note that this may raise a validation error pre_save_maplayer.send(self, layer_metadata=metadata_layer, mode=mode_editing, data=data_extra_fields, user=self.request.user) # Validate and save try: original_feature = None feature = QgsFeature(qgis_layer.fields()) if mode_editing == EDITING_POST_DATA_UPDATED: # add patch for shapefile type, geojson_feature['id'] id int() instead of str() # path to fix into QGIS api geojson_feature[ 'id'] = get_layer_fids_from_server_fids( [str(geojson_feature['id'])], qgis_layer)[0] feature.setId(geojson_feature['id']) # Get feature from data provider before update original_feature = qgis_layer.getFeature( geojson_feature['id']) # We use this feature for geometry parsing only: imported_feature = QgsJsonUtils.stringToFeatureList( json.dumps(geojson_feature), qgis_layer.fields(), None # UTF8 codec )[0] feature.setGeometry(imported_feature.geometry()) # There is something wrong in QGIS 3.10 (fixed in later versions) # so, better loop through the fields and set attributes individually for name, value in geojson_feature['properties'].items( ): feature.setAttribute(name, value) # Loop again for set expressions value: # For update store expression result to use later into update condition field_expresion_values = {} for qgis_field in qgis_layer.fields(): if qgis_field.defaultValueDefinition().expression( ): exp = QgsExpression( qgis_field.defaultValueDefinition( ).expression()) if exp.rootNode().nodeType( ) != QgsExpressionNode.ntLiteral and not exp.hasParserError( ): context = QgsExpressionContextUtils.createFeatureBasedContext( feature, qgis_layer.fields()) context.appendScopes( QgsExpressionContextUtils. globalProjectLayerScopes(qgis_layer)) result = exp.evaluate(context) if not exp.hasEvalError(): feature.setAttribute( qgis_field.name(), result) # Check update if expression default value has to run also on update e not # only on insert newone if qgis_field.defaultValueDefinition( ).applyOnUpdate(): field_expresion_values[ qgis_field.name()] = result elif qgis_field.typeName() in ('date', 'datetime', 'time'): if qgis_field.typeName() == 'date': qtype = QDate elif qgis_field.typeName() == 'datetime': qtype = QDateTime else: qtype = QTime field_idx = qgis_layer.fields().indexFromName( qgis_field.name()) options = qgis_layer.editorWidgetSetup( field_idx).config() if 'field_iso_format' in options and not options[ 'field_iso_format']: if geojson_feature['properties'][ qgis_field.name()]: value = qtype.fromString( geojson_feature['properties'][ qgis_field.name()], options['field_format']) feature.setAttribute( qgis_field.name(), value) # Call validator! errors = feature_validator(feature, metadata_layer.qgis_layer) if errors: raise ValidationError(errors) # Save the feature if mode_editing == EDITING_POST_DATA_ADDED: if has_transactions: if not qgis_layer.addFeature(feature): raise Exception( _('Error adding feature: %s') % ', '.join(qgis_layer.dataProvider(). errors())) else: if not qgis_layer.dataProvider().addFeature( feature): raise Exception( _('Error adding feature: %s') % ', '.join(qgis_layer.dataProvider(). errors())) # Patch for Spatialite provider on pk if qgis_layer.dataProvider().name( ) == 'spatialite': pks = qgis_layer.primaryKeyAttributes() if len(pks) > 1: raise Exception( _(f'Error adding feature on Spatialite provider: ' f'layer {qgis_layer.id()} has more than one pk column' )) # update pk attribute: feature.setAttribute( pks[0], server_fid(feature, qgis_layer.dataProvider())) elif mode_editing == EDITING_POST_DATA_UPDATED: attr_map = {} for name, value in geojson_feature[ 'properties'].items(): if name in qgis_layer.dataProvider( ).fieldNameMap(): if name in field_expresion_values: value = field_expresion_values[name] attr_map[qgis_layer.dataProvider(). fieldNameMap()[name]] = value if has_transactions: if not qgis_layer.changeAttributeValues( geojson_feature['id'], attr_map): raise Exception( _('Error changing attribute values: %s' ) % ', '.join(qgis_layer.dataProvider(). errors())) # Check for errors because of https://github.com/qgis/QGIS/issues/36583 if qgis_layer.dataProvider().errors(): raise Exception(', '.join( qgis_layer.dataProvider().errors())) if not feature.geometry().isNull( ) and not qgis_layer.changeGeometry( geojson_feature['id'], feature.geometry()): raise Exception( _('Error changing geometry: %s') % ', '.join(qgis_layer.dataProvider(). errors())) else: if not qgis_layer.dataProvider( ).changeAttributeValues( {geojson_feature['id']: attr_map}): raise Exception( _('Error changing attribute values: %s' ) % ', '.join(qgis_layer.dataProvider(). errors())) if not feature.geometry().isNull( ) and not qgis_layer.dataProvider( ).changeGeometryValues({ geojson_feature['id']: feature.geometry() }): raise Exception( _('Error changing geometry: %s') % ', '.join(qgis_layer.dataProvider(). errors())) to_res = {} to_res_lock = {} if mode_editing == EDITING_POST_DATA_ADDED: # to exclude QgsFormater used into QgsJsonjExporter is necessary build by hand single json feature ex = QgsJsonExporter(qgis_layer) ex.setIncludeAttributes(False) fnames = [f.name() for f in feature.fields()] jfeature = json.loads( ex.exportFeature( feature, dict(zip(fnames, feature.attributes())))) to_res.update({ 'clientid': geojson_feature['id'], # This might be the internal QGIS feature id (< 0) 'id': server_fid( feature, metadata_layer.qgis_layer.dataProvider()), 'properties': jfeature['properties'] }) # lock news: to_res_lock = metadata_layer.lock.modelLock2dict( metadata_layer.lock.lockFeature(server_fid( feature, metadata_layer.qgis_layer.dataProvider()), save=True)) if bool(to_res): insert_ids.append(to_res) if bool(to_res_lock): lock_ids.append(to_res_lock) # Send post vase signal post_save_maplayer.send( self, layer_metadata=metadata_layer, mode=mode_editing, data=data_extra_fields, user=self.request.user, original_feature=original_feature, to_res=to_res) except ValidationError as ex: raise ValidationError({ metadata_layer.client_var: { mode_editing: { 'id': geojson_feature['id'], 'fields': ex.detail, } } }) except Exception as ex: raise ValidationError({ metadata_layer.client_var: { mode_editing: { 'id': geojson_feature['id'], 'fields': str(ex), } } }) # erasing feature if to do if EDITING_POST_DATA_DELETED in post_layer_data: fids = post_layer_data[EDITING_POST_DATA_DELETED] # get feature fids from server fids from client. fids = get_layer_fids_from_server_fids([str(id) for id in fids], qgis_layer) for feature_id in fids: # control feature locked if not metadata_layer.lock.checkFeatureLocked(str(feature_id)): raise Exception( self.no_more_lock_feature_msg.format( feature_id, metadata_layer.client_var)) # Get feature to delete ex = QgsJsonExporter(qgis_layer) deleted_feature = ex.exportFeature( qgis_layer.getFeature(feature_id)) pre_delete_maplayer.send(self, layer_metatada=metadata_layer, data=deleted_feature, user=self.request.user) qgis_layer.dataProvider().clearErrors() if has_transactions: if not qgis_layer.deleteFeatures( [feature_id]) or qgis_layer.dataProvider().errors(): raise Exception( _('Cannot delete feature: %s') % ', '.join(qgis_layer.dataProvider().errors())) else: if not qgis_layer.dataProvider().deleteFeatures( [feature_id]) or qgis_layer.dataProvider().errors(): raise Exception( _('Cannot delete feature: %s') % ', '.join(qgis_layer.dataProvider().errors())) return insert_ids, lock_ids
def expression_eval(expression_text, project_id=None, qgs_layer_id=None, form_data=None, formatter=0): """Evaluates a QgsExpression and returns the result :param expression_text: The QgsExpression text :type expression_text: str :param project_id: ID of the qdjango project, defaults to None :type project_id: int, optional :param qgs_layer_id: ID of the QGIS Layer, defaults to None :type qgslayer_id: str, optional :param form_data: A dictionary that maps to a GeoJSON representation of the feature currently edited in the form :type form_data: dict, optional :param formatter: Indicate if form_data values contains formatter values or original features value. :type formatter: int, optional """ expression = QgsExpression(expression_text) expression_context = QgsExpressionContext() layer = None for func_name in expression.referencedFunctions(): if func_name in FORBIDDEN_FUNCTIONS: raise ExpressionForbiddenError( _('Function "{}" is not allowed for security reasons!').format( func_name)) for var_name in expression.referencedVariables(): if var_name in FORBIDDEN_VARIABLES: raise ExpressionForbiddenError( _('Variable "{}" is not allowed for security reasons!').format( var_name)) if project_id is not None: try: project = Project.objects.get(pk=project_id) if qgs_layer_id is not None: try: layer = project.layer_set.get(qgs_layer_id=qgs_layer_id) except Layer.DoesNotExist: raise ExpressionLayerError( _('QGIS layer with id "{}" could not be found!'). format(qgs_layer_id)) expression_contex = QgsExpressionContextUtils.globalProjectLayerScopes( layer.qgis_layer) else: expression_contex = QgsExpressionContextUtils.globalScope() expression_context.appendScope( QgsExpressionContextUtils.projectScope( project.qgis_project)) except Project.DoesNotExist: raise ExpressionProjectError( _('QDjango project with id "{}" could not be found!').format( project_id)) else: expression_contex = QgsExpressionContextUtils.globalScope() if form_data is not None: if layer is None: raise ExpressionLayerError( _('A valid QGIS layer is required to process form data!')) try: # Case by formatter # formatter == 1 : get featureid from layer, usually must be used with formatter form_data # formatter == 0 : default behavior if formatter == 0: fields = layer.qgis_layer.fields() form_feature = QgsJsonUtils.stringToFeatureList( json.dumps(form_data), fields, None)[0] # Set attributes manually because QgsJsonUtils does not respect order for k, v in form_data['properties'].items(): form_feature.setAttribute(k, v) else: qgis_feature_request = QgsFeatureRequest() exp = expression_from_server_fids( [form_data['id']], layer.qgis_layer.dataProvider()) qgis_feature_request.combineFilterExpression(exp) form_feature = get_qgis_features(layer.qgis_layer, qgis_feature_request)[0] expression_context.appendScope( QgsExpressionContextUtils.formScope(form_feature)) expression_context.setFeature(form_feature) except: raise ExpressionFormDataError() valid, errors = expression.checkExpression(expression_text, expression_context) if not valid: raise ExpressionParseError(errors) result = expression.evaluate(expression_context) if expression.hasEvalError(): raise ExpressionEvalError(expression.evalErrorString()) return result
def save_vector_data(self, metadata_layer, post_layer_data, has_transactions, post_save_signal=True, **kwargs): """Save vector editing data :param metadata_layer: metadata of the layer being edited :type metadata_layer: MetadataVectorLayer :param post_layer_data: post data with 'add', 'delete' etc. :type post_layer_data: dict :param has_transactions: true if the layer support transactions :type has_transactions: bool :param post_save_signal: if this is a post_save_signal call, defaults to True :type post_save_signal: bool, optional """ # get initial featurelocked metadata_layer.lock.getInitialFeatureLockedIds() # get lockids from client metadata_layer.lock.setLockeFeaturesFromClient( post_layer_data['lockids']) # data for response insert_ids = list() lock_ids = list() # FIXME: check this out # for add check if is a metadata_layer and referenced field is a pk is_referenced_field_is_pk = 'referenced_layer_insert_ids' in kwargs and kwargs['referenced_layer_insert_ids'] \ and hasattr(metadata_layer, 'referenced_field_is_pk') \ and metadata_layer.referenced_field_is_pk # Get the layer qgis_layer = metadata_layer.qgis_layer for mode_editing in (EDITING_POST_DATA_ADDED, EDITING_POST_DATA_UPDATED): if mode_editing in post_layer_data: for geojson_feature in post_layer_data[mode_editing]: data_extra_fields = {'feature': geojson_feature} # Clear any old error qgis_layer.dataProvider().clearErrors() # add media data self.add_media_property(geojson_feature, metadata_layer) # for GEOSGeometry of Django 2.2 it must add crs to feature if is not set if a geo feature if metadata_layer.geometry_type != QGIS_LAYER_TYPE_NO_GEOM: self.add_crs_to_feature(geojson_feature) # reproject data if necessary if self.reproject: self.reproject_feature(geojson_feature, to_layer=True) # case relation data ADD, if father referenced field is pk if is_referenced_field_is_pk: for newid in kwargs['referenced_layer_insert_ids']: if geojson_feature['properties'][ metadata_layer. referencing_field] == newid['clientid']: geojson_feature['properties'][ metadata_layer. referencing_field] = newid['id'] if mode_editing == EDITING_POST_DATA_UPDATED: # control feature locked if not metadata_layer.lock.checkFeatureLocked( geojson_feature['id']): raise Exception( self.no_more_lock_feature_msg.format( geojson_feature['id'], metadata_layer.client_var)) # Send for validation # Note that this may raise a validation error pre_save_maplayer.send(self, layer_metadata=metadata_layer, mode=mode_editing, data=data_extra_fields, user=self.request.user) # Validate and save try: feature = QgsFeature(qgis_layer.fields()) if mode_editing == EDITING_POST_DATA_UPDATED: feature.setId(geojson_feature['id']) # We use this feature for geometry parsing only: imported_feature = QgsJsonUtils.stringToFeatureList( json.dumps(geojson_feature), qgis_layer.fields(), None # UTF8 codec )[0] feature.setGeometry(imported_feature.geometry()) # There is something wrong in QGIS 3.10 (fixed in later versions) # so, better loop through the fields and set attributes individually for name, value in geojson_feature['properties'].items( ): feature.setAttribute(name, value) # Call validator! errors = feature_validator(feature, metadata_layer.qgis_layer) if errors: raise ValidationError(errors) # Save the feature if mode_editing == EDITING_POST_DATA_ADDED: if has_transactions: if not qgis_layer.addFeature(feature): raise Exception( _('Error adding feature: %s') % ', '.join(qgis_layer.dataProvider(). errors())) else: if not qgis_layer.dataProvider().addFeature( feature): raise Exception( _('Error adding feature: %s') % ', '.join(qgis_layer.dataProvider(). errors())) # Patch for Spatialite provider on pk if qgis_layer.dataProvider().name( ) == 'spatialite': pks = qgis_layer.primaryKeyAttributes() if len(pks) > 1: raise Exception( _(f'Error adding feature on Spatialite provider: ' f'layer {qgis_layer.id()} has more than one pk column' )) # update pk attribute: feature.setAttribute(pks[0], feature.id()) elif mode_editing == EDITING_POST_DATA_UPDATED: attr_map = {} for name, value in geojson_feature[ 'properties'].items(): if name in qgis_layer.dataProvider( ).fieldNameMap(): attr_map[qgis_layer.dataProvider(). fieldNameMap()[name]] = value if has_transactions: if not qgis_layer.changeAttributeValues( geojson_feature['id'], attr_map): raise Exception( _('Error changing attribute values: %s' ) % ', '.join(qgis_layer.dataProvider(). errors())) # Check for errors because of https://github.com/qgis/QGIS/issues/36583 if qgis_layer.dataProvider().errors(): raise Exception(', '.join( qgis_layer.dataProvider().errors())) if not feature.geometry().isNull( ) and not qgis_layer.changeGeometry( geojson_feature['id'], feature.geometry()): raise Exception( _('Error changing geometry: %s') % ', '.join(qgis_layer.dataProvider(). errors())) else: if not qgis_layer.dataProvider( ).changeAttributeValues( {geojson_feature['id']: attr_map}): raise Exception( _('Error changing attribute values: %s' ) % ', '.join(qgis_layer.dataProvider(). errors())) if not feature.geometry().isNull( ) and not qgis_layer.dataProvider( ).changeGeometryValues({ geojson_feature['id']: feature.geometry() }): raise Exception( _('Error changing geometry: %s') % ', '.join(qgis_layer.dataProvider(). errors())) to_res = {} to_res_lock = {} if mode_editing == EDITING_POST_DATA_ADDED: ex = QgsJsonExporter(qgis_layer) jfeature = json.loads(ex.exportFeature(feature)) to_res.update({ 'clientid': geojson_feature['id'], # This might be the internal QGIS feature id (< 0) 'id': feature.id(), 'properties': jfeature['properties'] }) # lock news: to_res_lock = metadata_layer.lock.modelLock2dict( metadata_layer.lock.lockFeature(feature.id(), save=True)) if bool(to_res): insert_ids.append(to_res) if bool(to_res_lock): lock_ids.append(to_res_lock) except ValidationError as ex: raise ValidationError({ metadata_layer.client_var: { mode_editing: { 'id': geojson_feature['id'], 'fields': ex.detail, } } }) except Exception as ex: raise ValidationError({ metadata_layer.client_var: { mode_editing: { 'id': geojson_feature['id'], 'fields': str(ex), } } }) # erasing feature if to do if EDITING_POST_DATA_DELETED in post_layer_data: for feature_id in post_layer_data[EDITING_POST_DATA_DELETED]: # control feature locked if not metadata_layer.lock.checkFeatureLocked(feature_id): raise Exception( self.no_more_lock_feature_msg.format( feature_id, metadata_layer.client_var)) # FIXME: pre_delete_maplayer # pre_delete_maplayer.send(metadata_layer.serializer, layer=metadata_layer.layer_id, # data=serializer.data, user=self.request.user) qgis_layer.dataProvider().clearErrors() if has_transactions: if not qgis_layer.deleteFeatures( [feature_id]) or qgis_layer.dataProvider().errors(): raise Exception( _('Cannot delete feature: %s') % ', '.join(qgis_layer.dataProvider().errors())) else: if not qgis_layer.dataProvider().deleteFeatures( [feature_id]) or qgis_layer.dataProvider().errors(): raise Exception( _('Cannot delete feature: %s') % ', '.join(qgis_layer.dataProvider().errors())) return insert_ids, lock_ids
def zoomToPressed(self): try: text = self.coordTxt.text().strip() if text[0] == '{': # This may be a GeoJSON point codec = QTextCodec.codecForName("UTF-8") fields = QgsJsonUtils.stringToFields(text, codec) fet = QgsJsonUtils.stringToFeatureList(text, fields, codec) if (len(fet) == 0) or not fet[0].isValid(): raise ValueError('Invalid Coordinates') geom = fet[0].geometry() if geom.isEmpty() or (geom.wkbType() != QgsWkbTypes.Point): raise ValueError('Invalid GeoJSON Geometry') pt = geom.asPoint() lat = pt.y() lon = pt.x() srcCrs = epsg4326 elif self.settings.zoomToProjIsWgs84(): if re.search(r'POINT\(', text) == None: lat, lon = LatLon.parseDMSString( text, self.settings.zoomToCoordOrder) else: m = re.findall( r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) srcCrs = epsg4326 elif self.settings.zoomToProjIsPlusCodes() and olc.isValid(text): # This looks like a Plus Codes coordinate coord = olc.decode(text) srcCrs = epsg4326 lat = coord.latitudeCenter lon = coord.longitudeCenter elif self.settings.zoomToProjIsMGRS(): # This is an MGRS coordinate text = re.sub(r'\s+', '', str(text)) # Remove all white space lat, lon = mgrs.toWgs(text) srcCrs = epsg4326 else: # Is either the project or custom CRS if re.search(r'POINT\(', text) == None: coords = re.split(r'[\s,;:]+', text, 1) if len(coords) < 2: raise ValueError('Invalid Coordinates') if self.settings.zoomToCoordOrder == self.settings.OrderYX: lat = float(coords[0]) lon = float(coords[1]) else: lon = float(coords[0]) lat = float(coords[1]) else: m = re.findall( r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) if self.settings.zoomToProjIsProjectCRS(): srcCrs = self.canvas.mapSettings().destinationCrs() else: srcCrs = self.settings.zoomToCustomCRS() pt = self.lltools.zoomTo(srcCrs, lat, lon) if self.settings.persistentMarker: if self.marker is None: self.marker = QgsVertexMarker(self.canvas) self.marker.setCenter(pt) self.marker.setIconSize(18) self.marker.setPenWidth(2) self.marker.setIconType(QgsVertexMarker.ICON_CROSS) elif self.marker is not None: self.removeMarker() except: #traceback.print_exc() self.iface.messageBar().pushMessage("", "Invalid Coordinate", level=Qgis.Warning, duration=2) return
def to_representation(self, instance): ret = super(WidgetSerializer, self).to_representation(instance) ret['type'] = instance.widget_type # get edittype edittypes = eval(self.layer.edittypes) if ret['type'] == 'search': body = json.loads(instance.body) ret['options'] = { 'queryurl': None, 'title': body['title'], 'results': body['results'], 'filter': [], 'dozoomtoextent': body['dozoomtoextent'], #'zoom': body['zoom'], } for field in body['fields']: # if widgettype is selectbox, get values if 'widgettype' in field and field['widgettype'] == 'selectbox': qgis_layer = get_qgis_layer(self.layer) uniques = qgis_layer.uniqueValues( qgis_layer.fields().indexOf(field['name'])) values = [] for u in uniques: try: values.append( json.loads(QgsJsonUtils.encodeValue(u))) except Exception as e: logger.error(f'Response vector widget unique: {e}') continue field['input']['type'] = 'selectfield' if 'dependance' not in field['input']['options']: # check if field has a widget edit type # todo: add 'ValueRelation' case if field['name'] in edittypes and \ edittypes[field['name']]['widgetv2type'] in ('ValueMap'): field['input']['options']['values'] = edittypes[ field['name']]['values'] else: field['input']['options']['values'] = values else: field['input']['options']['values'] = [] #For AutoccOmpleteBox imput type if 'widgettype' in field and field[ 'widgettype'] == 'autocompletebox': field['input']['type'] = 'autocompletefield' input = field['input'] input['options']['blanktext'] = field['blanktext'] ret['options']['filter'].append({ 'op': field['filterop'], 'attribute': field['name'], 'label': field['label'], 'input': input, 'logicop': field.get('logicop', 'AND').upper() }) else: ret['body'] = json.loads(instance.body) return ret
def testEncodeValue(self): """ test encoding various values for use in GeoJSON strings """ self.assertEqual(QgsJsonUtils.encodeValue(NULL), 'null') self.assertEqual(QgsJsonUtils.encodeValue(5), '5') self.assertEqual(QgsJsonUtils.encodeValue(5.9), '5.9') self.assertEqual(QgsJsonUtils.encodeValue(5999999999), '5999999999') self.assertEqual(QgsJsonUtils.encodeValue('string'), '"string"') self.assertEqual(QgsJsonUtils.encodeValue('str\ning'), '"str\\ning"') self.assertEqual(QgsJsonUtils.encodeValue('str\ring'), '"str\\ring"') self.assertEqual(QgsJsonUtils.encodeValue('str\bing'), '"str\\bing"') self.assertEqual(QgsJsonUtils.encodeValue('str\ting'), '"str\\ting"') self.assertEqual(QgsJsonUtils.encodeValue('str\\ing'), '"str\\\\ing"') self.assertEqual(QgsJsonUtils.encodeValue('str\\ning'), '"str\\\\ning"') self.assertEqual(QgsJsonUtils.encodeValue('str\n\\\\ing'), '"str\\n\\\\\\\\ing"') self.assertEqual(QgsJsonUtils.encodeValue('str/ing'), '"str\\/ing"') self.assertEqual(QgsJsonUtils.encodeValue([5, 6]), '[5,6]') self.assertEqual(QgsJsonUtils.encodeValue(['a', 'b', 'c']), '["a","b","c"]') self.assertEqual(QgsJsonUtils.encodeValue(['a', 3, 'c']), '["a",3,"c"]') self.assertEqual(QgsJsonUtils.encodeValue(['a', 'c\nd']), '["a","c\\nd"]') # handle differences due to Qt5 version, where compact output now lacks \n enc_str = QgsJsonUtils.encodeValue({'key': 'value', 'key2': 5}) self.assertTrue(enc_str == '{"key":"value",\n"key2":5}' or enc_str == '{"key":"value","key2":5}') enc_str = QgsJsonUtils.encodeValue({ 'key': [1, 2, 3], 'key2': { 'nested': 'nested\\result' } }) self.assertTrue( enc_str == '{"key":[1,2,3],\n"key2":{"nested":"nested\\\\result"}}' or enc_str == '{"key":[1,2,3],"key2":{"nested":"nested\\\\result"}}')
def addFeature(self): text = self.lineEdit.text().strip() if text == "": return layer = self.iface.activeLayer() if layer == None: return try: if (self.inputProjection == 0) or (text[0] == '{'): # If this is GeoJson it does not matter what inputProjection is if text[0] == '{': # This may be a GeoJSON point codec = QTextCodec.codecForName("UTF-8") fields = QgsJsonUtils.stringToFields(text, codec) fet = QgsJsonUtils.stringToFeatureList(text, fields, codec) if (len(fet) == 0) or not fet[0].isValid(): raise ValueError('Invalid Coordinates') geom = fet[0].geometry() if geom.isEmpty() or (geom.wkbType() != QgsWkbTypes.Point): raise ValueError('Invalid GeoJSON Geometry') pt = geom.asPoint() lat = pt.y() lon = pt.x() elif re.search(r'POINT\(', text) != None: m = re.findall(r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) else: lat, lon = LatLon.parseDMSString(text, self.inputXYOrder) srcCrs = epsg4326 elif self.inputProjection == 1: # This is an MGRS coordinate text = re.sub(r'\s+', '', unicode(text)) # Remove all white space lat, lon = mgrs.toWgs(text) srcCrs = epsg4326 elif self.inputProjection == 4: text = text.strip() coord = olc.decode(text) lat = coord.latitudeCenter lon = coord.longitudeCenter srcCrs = epsg4326 else: # Is either the project or custom CRS if re.search(r'POINT\(', text) == None: coords = re.split(r'[\s,;:]+', text, 1) if len(coords) < 2: raise ValueError('Invalid Coordinates') if self.inputXYOrder == 0: # Y, X Order lat = float(coords[0]) lon = float(coords[1]) else: lon = float(coords[0]) lat = float(coords[1]) else: m = re.findall(r'POINT\(\s*([+-]?\d*\.?\d*)\s+([+-]?\d*\.?\d*)', text) if len(m) != 1: raise ValueError('Invalid Coordinates') lon = float(m[0][0]) lat = float(m[0][1]) if self.inputProjection == 2: # Project CRS srcCrs = self.canvas.mapSettings().destinationCrs() else: srcCrs = QgsCoordinateReferenceSystem(self.inputCustomCRS) except: #traceback.print_exc() self.iface.messageBar().pushMessage("", "Invalid Coordinate" , level=Qgis.Warning, duration=2) return self.lineEdit.clear() caps = layer.dataProvider().capabilities() if caps & QgsVectorDataProvider.AddFeatures: destCRS = layer.crs() # Get the CRS of the layer we are adding a point toWgs transform = QgsCoordinateTransform(srcCrs, destCRS, QgsProject.instance()) # Transform the input coordinate projection to the layer CRS x, y = transform.transform(float(lon), float(lat)) feat = QgsFeature(layer.fields()) feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y))) if layer.fields().count() == 0: layer.addFeature(feat) self.lltools.zoomTo(srcCrs, lat, lon) else: if self.iface.openFeatureForm(layer, feat): layer.addFeature(feat) self.lltools.zoomTo(srcCrs, lat, lon)