def eval_expression(expr_text, extra_data, default=None): """ Helper method to evaluate an expression. E.g. eval_expression("1+a", {"a": 2}) will return 3 """ if expr_text is None or len(expr_text) == 0: return default flds = QgsFields() for extra_col, extra_value in extra_data.items(): if isinstance(extra_value, int): t = QVariant.Int elif isinstance(extra_value, float): t = QVariant.Double else: t = QVariant.String flds.append(QgsField(extra_col, t)) f = QgsFeature(flds) for extra_col, extra_value in extra_data.items(): f[extra_col] = extra_value expr = QgsExpression(expr_text) ctx = QgsExpressionContext() ctx.setFeature(f) res = expr.evaluate(ctx) return default if expr.hasEvalError() else res
def testVariables(self): """ check through widget model to ensure it is populated with variables """ w = QgsExpressionBuilderWidget() m = w.model() s = QgsExpressionContextScope() s.setVariable('my_var1', 'x') s.setVariable('my_var2', 'y') c = QgsExpressionContext() c.appendScope(s) # check that variables are added when setting context w.setExpressionContext(c) items = m.findItems('my_var1', Qt.MatchRecursive) self.assertEqual(len(items), 1) items = m.findItems('my_var2', Qt.MatchRecursive) self.assertEqual(len(items), 1) items = m.findItems('not_my_var', Qt.MatchRecursive) self.assertEqual(len(items), 0) # double check that functions are still only there once items = m.findItems('lower', Qt.MatchRecursive) self.assertEqual(len(items), 1) items = m.findItems('upper', Qt.MatchRecursive) self.assertEqual(len(items), 1)
def test_post_multipart_action(self): """Test multipart""" self.body = None def _req_logger(self, params): self.body = bytes(params.content()) QgsNetworkAccessManager.instance( ).requestAboutToBeCreated[QgsNetworkRequestParameters].connect( partial(_req_logger, self)) temp_dir = QTemporaryDir() temp_path = temp_dir.path() temp_file = os.path.join(temp_path, 'multipart.txt') action = QgsAction( QgsAction.SubmitUrlMultipart, 'url_encoded', "http://fake_qgis_http_endpoint" + temp_file + r"?[% url_encode(map('a&+b', 'a and plus b', 'a=b', 'a equals b')) %]" ) ctx = QgsExpressionContext() action.run(ctx) while not self.body: QgsApplication.instance().processEvents() self.assertEqual( re.sub(r'\.oOo\.[^\r]*', '.oOo.UUID', self.body.decode('utf8')), '\r\n'.join([ '--boundary_.oOo.UUID', 'Content-Disposition: form-data; name="a&+b"', '', 'a and plus b', '--boundary_.oOo.UUID', 'Content-Disposition: form-data; name="a=b"', '', 'a equals b', '--boundary_.oOo.UUID', '' ]))
def print_layout(project, layout_name, feature_filter: str = None, scales=None, scale=None, **kwargs): """Generate a PDF for an atlas or a report. :param project: The QGIS project. :type project: QgsProject :param layout_name: Name of the layout of the atlas or report. :type layout_name: basestring :param feature_filter: QGIS Expression to use to select the feature. It can return many features, a multiple pages PDF will be returned. This is required to print atlas, not report :type feature_filter: basestring :param scale: A scale to force in the atlas context. Default to None. :type scale: int :param scales: A list of predefined list of scales to force in the atlas context. Default to None. :type scales: list :return: Path to the PDF. :rtype: basestring """ canvas = QgsMapCanvas() bridge = QgsLayerTreeMapCanvasBridge(project.layerTreeRoot(), canvas) bridge.setCanvasLayers() manager = project.layoutManager() master_layout = manager.layoutByName(layout_name) settings = QgsLayoutExporter.PdfExportSettings() atlas = None atlas_layout = None report_layout = None logger = Logger() if not master_layout: raise AtlasPrintException('Layout `{}` not found'.format(layout_name)) if master_layout.layoutType() == QgsMasterLayoutInterface.PrintLayout: for _print_layout in manager.printLayouts(): if _print_layout.name() == layout_name: atlas_layout = _print_layout break atlas = atlas_layout.atlas() if not atlas.enabled(): raise AtlasPrintException('The layout is not enabled for an atlas') layer = atlas.coverageLayer() if feature_filter is None: raise AtlasPrintException( 'EXP_FILTER is mandatory to print an atlas layout') feature_filter = optimize_expression(layer, feature_filter) expression = QgsExpression(feature_filter) if expression.hasParserError(): raise AtlasPrintException( 'Expression is invalid, parser error: {}'.format( expression.parserErrorString())) context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope(project)) context.appendScope( QgsExpressionContextUtils.layoutScope(atlas_layout)) context.appendScope(QgsExpressionContextUtils.atlasScope(atlas)) context.appendScope(QgsExpressionContextUtils.layerScope(layer)) expression.prepare(context) if expression.hasEvalError(): raise AtlasPrintException( 'Expression is invalid, eval error: {}'.format( expression.evalErrorString())) atlas.setFilterFeatures(True) atlas.setFilterExpression(feature_filter) if scale: atlas_layout.referenceMap().setAtlasScalingMode( QgsLayoutItemMap.Fixed) atlas_layout.referenceMap().setScale(scale) if scales: atlas_layout.referenceMap().setAtlasScalingMode( QgsLayoutItemMap.Predefined) if Qgis.QGIS_VERSION_INT >= 30900: settings.predefinedMapScales = scales else: atlas_layout.reportContext().setPredefinedScales(scales) if not scales and atlas_layout.referenceMap().atlasScalingMode( ) == QgsLayoutItemMap.Predefined: if Qgis.QGIS_VERSION_INT >= 30900: use_project = project.useProjectScales() map_scales = project.mapScales() else: map_scales = project_scales(project) use_project = len(map_scales) == 0 if not use_project or len(map_scales) == 0: logger.info( 'Map scales not found in project, fetching predefined map scales in global config' ) map_scales = global_scales() if Qgis.QGIS_VERSION_INT >= 30900: settings.predefinedMapScales = map_scales else: atlas_layout.reportContext().setPredefinedScales(map_scales) elif master_layout.layoutType() == QgsMasterLayoutInterface.Report: report_layout = master_layout else: raise AtlasPrintException('The layout is not supported by the plugin') for key, value in kwargs.items(): found = False if atlas_layout: item = atlas_layout.itemById(key.lower()) if isinstance(item, QgsLayoutItemLabel): item.setText(value) found = True logger.info( 'Additional parameters: {} found in layout {}, value {}'.format( key, found, value)) export_path = os.path.join(tempfile.gettempdir(), '{}_{}.pdf'.format(layout_name, uuid4())) result, error = QgsLayoutExporter.exportToPdf(atlas or report_layout, export_path, settings) if result != QgsLayoutExporter.Success and not os.path.isfile(export_path): raise Exception('export not generated {} ({})'.format( export_path, error)) return export_path
def processAlgorithm(self, progress): layer = dataobjects.getObjectFromUri( self.getParameterValue(self.INPUT_LAYER)) fieldName = self.getParameterValue(self.FIELD_NAME) fieldType = self.TYPES[self.getParameterValue(self.FIELD_TYPE)] width = self.getParameterValue(self.FIELD_LENGTH) precision = self.getParameterValue(self.FIELD_PRECISION) newField = self.getParameterValue(self.NEW_FIELD) formula = self.getParameterValue(self.FORMULA) output = self.getOutputFromName(self.OUTPUT_LAYER) fields = layer.fields() if newField: fields.append(QgsField(fieldName, fieldType, '', width, precision)) writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs()) exp = QgsExpression(formula) da = QgsDistanceArea() da.setSourceCrs(layer.crs().srsid()) da.setEllipsoidalMode( iface.mapCanvas().mapSettings().hasCrsTransformEnabled()) da.setEllipsoid(QgsProject.instance().readEntry( 'Measure', '/Ellipsoid', GEO_NONE)[0]) exp.setGeomCalculator(da) exp.setDistanceUnits(QgsProject.instance().distanceUnits()) exp.setAreaUnits(QgsProject.instance().areaUnits()) exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope()) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) if not exp.prepare(exp_context): raise GeoAlgorithmExecutionException( self.tr('Evaluation error: %s' % exp.evalErrorString())) outFeature = QgsFeature() outFeature.initAttributes(len(fields)) outFeature.setFields(fields) error = '' calculationSuccess = True features = vector.features(layer) total = 100.0 / len(features) rownum = 1 for current, f in enumerate(features): rownum = current + 1 exp_context.setFeature(f) exp_context.lastScope().setVariable("row_number", rownum) value = exp.evaluate(exp_context) if exp.hasEvalError(): calculationSuccess = False error = exp.evalErrorString() break else: outFeature.setGeometry(f.geometry()) for fld in f.fields(): outFeature[fld.name()] = f[fld.name()] outFeature[fieldName] = value writer.addFeature(outFeature) progress.setPercentage(int(current * total)) del writer if not calculationSuccess: raise GeoAlgorithmExecutionException( self.tr('An error occurred while evaluating the calculation ' 'string:\n%s' % error))
def processAlgorithm(self, context, feedback): layer = QgsProcessingUtils.mapLayerFromString( self.getParameterValue(self.INPUT_LAYER), context) fieldName = self.getParameterValue(self.FIELD_NAME) fieldType = self.TYPES[self.getParameterValue(self.FIELD_TYPE)] width = self.getParameterValue(self.FIELD_LENGTH) precision = self.getParameterValue(self.FIELD_PRECISION) newField = self.getParameterValue(self.NEW_FIELD) formula = self.getParameterValue(self.FORMULA) output = self.getOutputFromName(self.OUTPUT_LAYER) fields = layer.fields() if newField: fields.append(QgsField(fieldName, fieldType, '', width, precision)) writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context) exp = QgsExpression(formula) da = QgsDistanceArea() da.setSourceCrs(layer.crs()) da.setEllipsoid(QgsProject.instance().ellipsoid()) exp.setGeomCalculator(da) exp.setDistanceUnits(QgsProject.instance().distanceUnits()) exp.setAreaUnits(QgsProject.instance().areaUnits()) exp_context = QgsExpressionContext( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) if not exp.prepare(exp_context): raise GeoAlgorithmExecutionException( self.tr('Evaluation error: {0}').format(exp.evalErrorString())) outFeature = QgsFeature() outFeature.initAttributes(len(fields)) outFeature.setFields(fields) error = '' calculationSuccess = True features = QgsProcessingUtils.getFeatures(layer, context) total = 100.0 / QgsProcessingUtils.featureCount(layer, context) rownum = 1 for current, f in enumerate(features): rownum = current + 1 exp_context.setFeature(f) exp_context.lastScope().setVariable("row_number", rownum) value = exp.evaluate(exp_context) if exp.hasEvalError(): calculationSuccess = False error = exp.evalErrorString() break else: outFeature.setGeometry(f.geometry()) for fld in f.fields(): outFeature[fld.name()] = f[fld.name()] outFeature[fieldName] = value writer.addFeature(outFeature) feedback.setProgress(int(current * total)) del writer if not calculationSuccess: raise GeoAlgorithmExecutionException( self.tr('An error occurred while evaluating the calculation ' 'string:\n{0}').format(error))
def handleAlgorithmResults(alg, context, feedback=None, showResults=True, parameters={}): wrongLayers = [] if feedback is None: feedback = QgsProcessingFeedback() feedback.setProgressText( QCoreApplication.translate('Postprocessing', 'Loading resulting layers')) i = 0 for l, details in context.layersToLoadOnCompletion().items(): if feedback.isCanceled(): return False if len(context.layersToLoadOnCompletion()) > 2: # only show progress feedback if we're loading a bunch of layers feedback.setProgress( 100 * i / float(len(context.layersToLoadOnCompletion()))) try: layer = QgsProcessingUtils.mapLayerFromString( l, context, typeHint=details.layerTypeHint) if layer is not None: set_layer_name(layer, details) '''If running a model, the execution will arrive here when an algorithm that is part of that model is executed. We check if its output is a final otuput of the model, and adapt the output name accordingly''' outputName = details.outputName expcontext = QgsExpressionContext() scope = QgsExpressionContextScope() expcontext.appendScope(scope) for out in alg.outputDefinitions(): if out.name() not in parameters: continue outValue = parameters[out.name()] if hasattr(outValue, "sink"): outValue = outValue.sink.valueAsString(expcontext)[0] else: outValue = str(outValue) if outValue == l: outputName = out.name() break style = None if outputName: style = RenderingStyles.getStyle(alg.id(), outputName) if style is None: if layer.type() == QgsMapLayerType.RasterLayer: style = ProcessingConfig.getSetting( ProcessingConfig.RASTER_STYLE) else: if layer.geometryType() == QgsWkbTypes.PointGeometry: style = ProcessingConfig.getSetting( ProcessingConfig.VECTOR_POINT_STYLE) elif layer.geometryType() == QgsWkbTypes.LineGeometry: style = ProcessingConfig.getSetting( ProcessingConfig.VECTOR_LINE_STYLE) else: style = ProcessingConfig.getSetting( ProcessingConfig.VECTOR_POLYGON_STYLE) if style: layer.loadNamedStyle(style) details.project.addMapLayer( context.temporaryLayerStore().takeMapLayer(layer)) if details.postProcessor(): details.postProcessor().postProcessLayer( layer, context, feedback) else: wrongLayers.append(str(l)) except Exception: QgsMessageLog.logMessage( QCoreApplication.translate( 'Postprocessing', "Error loading result layer:") + "\n" + traceback.format_exc(), 'Processing', Qgis.Critical) wrongLayers.append(str(l)) i += 1 feedback.setProgress(100) if wrongLayers: msg = QCoreApplication.translate( 'Postprocessing', "The following layers were not correctly generated.") msg += "<ul>" + "".join(["<li>%s</li>" % lay for lay in wrongLayers]) + "</ul>" msg += QCoreApplication.translate( 'Postprocessing', "You can check the 'Log Messages Panel' in QGIS main window to find more information about the execution of the algorithm." ) feedback.reportError(msg) return len(wrongLayers) == 0
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 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 addRowInLayer(row, errTable, table_codif): """ Rows will be converted in a geometry thanks to codification. All attributes will be added thanks to QgsExpressionContext. Parameters ---------- row: list of list A row contains one or many list of points. errTable: list of list Contains points in error. Some points can be added after the end of this function. table_codif: dictionnary The codification file. See the information about qlsc format. """ # TODO: assert? code = row[CODE_POSITION][0] parameters = row[PARAM_POSITION] codif = table_codif['Codification'][code] layerName = codif['Layer'] layer = QgsVectorLayer(layerName) dim = 4 if QgsWkbTypes.hasZ(layer.dataProvider().wkbType()) else 3 geom = geomFromType(list(zip(*row[1:dim])), parameters, codif['GeometryType'], layer.geometryType()) if geom: layer.startEditing() fields = layer.fields() newFeature = QgsFeature(fields) newFeature.setGeometry(geom) for e in codif['Attributes']: # print(e, e[1], e[1].startswith('_att')) if e[1].startswith('_att'): # len('_att') == 4 try: nb = int(e[1][4:]) - 1 assert(nb >= 0) val = row[ATTRS_POSITION + nb][0] newFeature[e[0]] = val except: # print("attributes error") pass else: context = QgsExpressionContext() scope = QgsExpressionContextScope() try: exp = QgsExpression(e[1]) scope.setFeature(newFeature) context.appendScope(scope) newFeature[e[0]] = exp.evaluate(context) except: # print('key error') pass ret = layer.addFeature(newFeature) # if not ret: # print(ret) layer.commitChanges() layer.updateExtents() else: # can it happen? errTable.append(row)
def createExpressionContext(self) -> QgsExpressionContext: # pylint: disable=missing-docstring, no-self-use context = QgsExpressionContext() scope = QgsExpressionContextScope() context.appendScope(scope) context.appendScope(vl1.createExpressionContextScope()) return context
def createExpressionContext(self) -> QgsExpressionContext: # pylint: disable=missing-docstring, no-self-use context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('some_var', 10) context.appendScope(scope) return context
def testRemappingSink(self): """ Test remapping features """ fields = QgsFields() fields.append(QgsField('fldtxt', QVariant.String)) fields.append(QgsField('fldint', QVariant.Int)) fields.append(QgsField('fldtxt2', QVariant.String)) store = QgsFeatureStore(fields, QgsCoordinateReferenceSystem('EPSG:3857')) mapping_def = QgsRemappingSinkDefinition() mapping_def.setDestinationWkbType(QgsWkbTypes.Point) mapping_def.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326')) mapping_def.setDestinationCrs( QgsCoordinateReferenceSystem('EPSG:3857')) mapping_def.setDestinationFields(fields) mapping_def.addMappedField('fldtxt2', QgsProperty.fromField('fld1')) mapping_def.addMappedField( 'fldint', QgsProperty.fromExpression('@myval * fldint')) proxy = QgsRemappingProxyFeatureSink(mapping_def, store) self.assertEqual(proxy.destinationSink(), store) self.assertEqual(len(store), 0) incoming_fields = QgsFields() incoming_fields.append(QgsField('fld1', QVariant.String)) incoming_fields.append(QgsField('fldint', QVariant.Int)) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('myval', 2) context.appendScope(scope) context.setFields(incoming_fields) proxy.setExpressionContext(context) proxy.setTransformContext(QgsProject.instance().transformContext()) f = QgsFeature() f.setFields(incoming_fields) f.setAttributes(["test", 123]) f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(1, 2))) self.assertTrue(proxy.addFeature(f)) self.assertEqual(len(store), 1) self.assertEqual(store.features()[0].geometry().asWkt(1), 'Point (111319.5 222684.2)') self.assertEqual(store.features()[0].attributes(), [None, 246, 'test']) f2 = QgsFeature() f2.setAttributes(["test2", 457]) f2.setGeometry(QgsGeometry.fromWkt('LineString( 1 1, 2 2)')) f3 = QgsFeature() f3.setAttributes(["test3", 888]) f3.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(3, 4))) self.assertTrue(proxy.addFeatures([f2, f3])) self.assertEqual(len(store), 4) self.assertEqual(store.features()[1].attributes(), [None, 914, 'test2']) self.assertEqual(store.features()[2].attributes(), [None, 914, 'test2']) self.assertEqual(store.features()[3].attributes(), [None, 1776, 'test3']) self.assertEqual(store.features()[1].geometry().asWkt(1), 'Point (111319.5 111325.1)') self.assertEqual(store.features()[2].geometry().asWkt(1), 'Point (222639 222684.2)') self.assertEqual(store.features()[3].geometry().asWkt(1), 'Point (333958.5 445640.1)')
def layer_value(feature, layer, defaultconfig): if not canvas: roam.utils.warning( "No canvas set for using layer_values default function") return None layers = [] # layer name can also be a list of layers to search layername = defaultconfig['layer'] if isinstance(layername, basestring): layers.append(layername) else: layers = layername expression = defaultconfig['expression'] field = defaultconfig['field'] for searchlayer in layers: try: searchlayer = QgsMapLayerRegistry.instance().mapLayersByName( searchlayer)[0] except IndexError: RoamEvents.raisemessage( "Missing layer", "Unable to find layer used in widget expression {}".format( searchlayer), level=1) roam.utils.warning( "Unable to find expression layer {}".format(searchlayer)) return if feature.geometry(): rect = feature.geometry().boundingBox() if layer.geometryType() == QGis.Point: point = feature.geometry().asPoint() rect = QgsRectangle(point.x(), point.y(), point.x() + 10, point.y() + 10) rect.scale(20) rect = canvas.mapRenderer().mapToLayerCoordinates(layer, rect) rq = QgsFeatureRequest().setFilterRect(rect) \ .setFlags(QgsFeatureRequest.ExactIntersect) features = searchlayer.getFeatures(rq) else: features = searchlayer.getFeatures() expression = expression.replace("$roamgeometry", "@roamgeometry") exp = QgsExpression(expression) exp.prepare(searchlayer.pendingFields()) if exp.hasParserError(): error = exp.parserErrorString() roam.utils.warning(error) context = QgsExpressionContext() scope = QgsExpressionContextScope() context.appendScope(scope) scope.setVariable("roamgeometry", feature.geometry()) for f in features: context.setFeature(f) value = exp.evaluate(context) if exp.hasEvalError(): error = exp.evalErrorString() roam.utils.warning(error) if value: return f[field] raise DefaultError('No features found')
def raster_to_polygon(raster_path, out_gpkg, out_layer_name, raster_value, surface_name='valley bottom'): # raster_layer = QgsRasterLayer(raster_path, 'in_raster') out_polygon_path = os.path.join(out_gpkg, out_layer_name) # --------- PROCESSING ------------- # -- DEM -- tempdir = tempfile.TemporaryDirectory() temp_raster = os.path.join(tempdir.name, "less_than.tif") gp_calc = processing.run('gdal:rastercalculator', {'INPUT_A': raster_path, 'BAND_A': 1, 'FORMULA': f'(A <= {raster_value})', 'OUTPUT': temp_raster}) # 'raster'}) # raster_less_than = gp_calc['OUTPUT'] raster_less_than = QgsRasterLayer(gp_calc['OUTPUT']) # -- DEM to VECTOR -- gp_raw = processing.run("gdal:polygonize", {'INPUT': raster_less_than, 'BAND': 1, 'FIELD': 'DN', 'EIGHT_CONNECTEDNESS': False, 'EXTRA': '', 'OUTPUT': 'TEMPORARY_OUTPUT'}) raw_vector = QgsVectorLayer( gp_raw['OUTPUT'], "raw_vectors", "ogr") # TODO remove when done # QgsProject.instance().addMapLayer(raw_vector) # -- CALCULATE AREA -- # create a provider pv = raw_vector.dataProvider() # add the attribute and update pv.addAttributes([QgsField('raw_area_m', QVariant.Int), QgsField( 'max_elev_m', QVariant.Double), QgsField('surface_name', QVariant.String)]) raw_vector.updateFields() # Create a context and scope context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(raw_vector)) # Loop through and add the areas delete_features = [] with edit(raw_vector): # loop them for feature in raw_vector.getFeatures(): if feature['DN'] != 1: delete_features.append(feature.id()) else: context.setFeature(feature) feature['raw_area_m'] = QgsExpression('$area').evaluate(context) feature['max_elev_m'] = raster_value feature['surface_name'] = surface_name raw_vector.updateFeature(feature) raw_vector.dataProvider().deleteFeatures(delete_features) # -- BUFFER POLYGONS -- gp_buffered = processing.run("native:buffer", {'INPUT': raw_vector, 'DISTANCE': 0.000001, 'SEGMENTS': 5, 'END_CAP_STYLE': 0, 'JOIN_STYLE': 0, 'MITER_LIMIT': 2, 'DISSOLVE': False, 'OUTPUT': 'TEMPORARY_OUTPUT'}) buffered_vector = gp_buffered['OUTPUT'] # TODO remove when final # QgsProject.instance().addMapLayer(buffered_vector) # -- Simplify Polygons -- gp_simple = processing.run("native:simplifygeometries", {'INPUT': buffered_vector, 'METHOD': 0, 'TOLERANCE': simplify_tolerance, 'OUTPUT': 'TEMPORARY_OUTPUT'}) simple_vector = gp_simple['OUTPUT'] # QgsVectorLayer( # gp_simplify['OUTPUT'], "simplified_polygons", 'ogr') # -- Smooth the polygons -- gp_smooth = processing.run("native:smoothgeometry", {'INPUT': simple_vector, 'ITERATIONS': 1, 'OFFSET': smoothing_offset, 'MAX_ANGLE': 180, 'OUTPUT': 'TEMPORARY_OUTPUT'}) smooth_vector = gp_smooth['OUTPUT'] # QgsVectorLayer( # , "smoothed_polygons", 'ogr') gp_multi = processing.run("native:multiparttosingleparts", {'INPUT': smooth_vector, 'OUTPUT': 'TEMPORARY_OUTPUT'}) multi_vector = gp_multi['OUTPUT'] # Fix any crossed geometry as final vector gp_fix = processing.run("native:fixgeometries", {'INPUT': multi_vector, 'OUTPUT': 'TEMPORARY_OUTPUT'}) final_vector = gp_fix['OUTPUT'] # Create a context and scope # Understand WTF this is?? context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(final_vector)) # add an area attribute # create a provider pv = final_vector.dataProvider() # add the attribute and update pv.addAttributes([QgsField('area_m', QVariant.Int)]) final_vector.updateFields() # Loop through and add the areas with edit(final_vector): # loop them for feature in final_vector.getFeatures(): context.setFeature(feature) feature['area_m'] = QgsExpression('$area').evaluate(context) final_vector.updateFeature(feature) # -- Delete Unneeded Fields -- pv = final_vector.dataProvider() pv.deleteAttributes([1, 2]) final_vector.updateFields() # -- Delete small polygons -- # data provider capabilities caps = final_vector.dataProvider().capabilities() # features and empty list of features to delete features = final_vector.getFeatures() delete_features = [] # if the layer can have deleted features if caps & QgsVectorDataProvider.DeleteFeatures: for feature in features: if feature['area_m'] <= polygon_min_size: delete_features.append(feature.id()) final_vector.dataProvider().deleteFeatures(delete_features) # TODO fix final data export options = QgsVectorFileWriter.SaveVectorOptions() options.layerName = out_layer_name options.driverName = 'GPKG' if os.path.exists(out_gpkg): options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer QgsVectorFileWriter.writeAsVectorFormat( final_vector, out_gpkg, options) # open the output layer QgsVectorLayer(out_polygon_path, out_layer_name, 'ogr')
def processAlgorithm(self, parameters, context, feedback): self.feedback = feedback """ Here is where the processing itself takes place. """ baseUrl = self.parameterAsString(parameters, self.BASE_URL, context) layers = self.parameterAsString(parameters, self.LAYERS, context) style_name = self.parameterAsString(parameters, self.STYLE_NAME, context) wmsVersion = self.parameterAsString(parameters, self.WMS_VERSION, context) imageFormatIdx = self.parameterAsEnum(parameters, self.IMAGE_FORMAT, context) pixelSize = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context) exprFileNameString = self.parameterAsString(parameters, self.FILENAME_FIELD, context) vectorLayer= self.parameterAsVectorLayer(parameters, self.INPUT_LAYER, context) self.path = self.parameterAsFile(parameters, self.DOWNLOAD_DIR, context) # fileNameFileIndex=-1 # if not fileNameField=="": # fileNameFileIndex = vectorLayer.fields().lookupField(fileNameField) exprFileName=QgsExpression(exprFileNameString) if exprFileName.hasParserError(): raise QgsProcessingException("Invalid Filename Expression: " + exprFileName.parserErrorString()) exprContext = QgsExpressionContext() if baseUrl=="": raise ### fields = vectorLayer.fields() fields.append( QgsField( self.fieldNameBBOX, QVariant.String ) ) fields.append( QgsField( self.fieldNameUrl, QVariant.String ) ) fields.append( QgsField( self.fieldNamePath, QVariant.String ) ) fields.append( QgsField( self.fieldNameFile, QVariant.String ) ) fields.append( QgsField( self.fieldNameResponseType, QVariant.String ) ) fields.append( QgsField( self.fieldNameHttpStatusCode, QVariant.Int) ) #take CRS from Project crsProject=QgsProject.instance().crs() feedback.pushInfo("CRS " + str(crsProject)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, vectorLayer.wkbType(), crsProject) total=1 #Check if any features are selected if vectorLayer.selectedFeatureCount() > 0: # Take only the selected features iter=vectorLayer.selectedFeatures() total = 100.0 / vectorLayer.selectedFeatureCount() else: iter = vectorLayer.getFeatures() try: total = 100.0 / vectorLayer.featureCount() #Division durch 0 except: msg = self.tr("no Features to process") feedback.reportError(msg) raise QgsProcessingException(msg) feedback.pushInfo("Pixel size: " + str(pixelSize)) countDownload=0 for i, feature in enumerate(iter): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): msg = self.tr("Process was canceled!") raise QgsProcessingException(msg) hasError=False loopText='' attrs=feature.attributes() geom = feature.geometry() bbox = geom.boundingBox() xMin = round( bbox.xMinimum(), 2) xMax = round( bbox.xMaximum(), 2) yMin = round( bbox.yMinimum(), 2) yMax = round( bbox.yMaximum(), 2) bboxText ='{0},{1},{2},{3}'.format( xMin, yMin, xMax, yMax ) pixelsX = int( (xMax - xMin) / pixelSize ) pixelsY = int( (yMax - yMin) / pixelSize ) feedback.pushInfo("BBox: " + str(i) + bboxText + " --> Tile width: " + str(pixelsX) + " height: " + str(pixelsY) ) epsg=vectorLayer.crs().authid() url = unicode( baseUrl + 'request=GetMap&Service=WMS&Version=' + wmsVersion + '&SRS=' + str(epsg) + '&LAYERS=' + layers + '&STYLES=' + style_name + '&BBOX=' + bboxText + '&FORMAT=image/' + self.imageFormats[imageFormatIdx].lower() +'&WIDTH=' + str(pixelsX)+ '&HEIGHT=' + str(pixelsY) ) #qUrl=QUrl(url) #get File type combo item fileExt = self.imageFormats[imageFormatIdx] exprContext.setFeature(feature) file_name = exprFileName.evaluate(exprContext) if file_name is None or file_name == '': file_name = "WMS_Tile_"+ str( i+1 ) feedback.pushInfo("Invalid Filename Expression on Feature: " + str(i) + " saved as " + file_name) contentType=None httpStatusCode=None writeModus = 'wb' # binary numTrials = 5 # 'Number of trial (Anzahl der Versuche) if type(file_name) == int or type(file_name) == float: # if filename is numeric file_name = str(file_name) else: #Replace Special Characters file_name=file_name.replace(":","_") file_name=file_name.replace("*","_") file_name=file_name.replace("\\","_") file_name=file_name.replace("/","_") if not str(url) == "": try: feedback.pushInfo( "----------------------------\n Start Download for feature " + str(i)+ " ("+ file_name +")" ) feedback.pushInfo( "\nGetMap-URL: "+ unicode(url) + "\n" ) #+ url" \n" + str(url.encode("utf-8"))+ result = self.getResponse(feedback, url, numTrials, 0) # % Versuche if result is not None: feedback.pushInfo(".. trials executed. ") # To Do: es gibt result is not null und trotzdem wird keine Looptext-Augabe oder Datei erzeugt try: httpStatusCode = result.status_code if not result.headers is None and not result.status_code is None: contentType = result.headers.get('Content-Type') contentType = contentType.lower() else: feedback.pushInfo("No Header and Status_Code" + str(i) +' '+ file_name + ' ('+str(httpStatusCode)+') ' + url) fileExt='xml' writeModus = 'w' except Exception as resultErr: #Result has No Header or status_code fileExt='xml' writeModus = 'w' loopText = loopText + "\n\t Download Result has No Header or status_code " + str(i) + "(" + file_name + "): " + " URL: " + unicode(url) + " " + str(resultErr.args) + ";" + str(repr(resultErr) + "\n HTTP Status Code: " + str( httpStatusCode ) + '\n Content-Type: ' + str(contentType) ) # change file type if not a image if contentType.find('image') == -1 and contentType.find('jpeg') == -1 and contentType.find('jpg') == -1 and contentType.find('png') == -1 and contentType.find('gif') == -1 and contentType.find('tif') == -1: isImage = False feedback.pushInfo("!!! Result is no valid WMS-image !!!!") writeModus = 'w' fileExt='xml' else: isImage=True feedback.pushInfo("contentType: " + str(contentType)) feedback.pushInfo('httpStatusCode: '+str( httpStatusCode )) feedback.pushInfo('Filetype: ' + str( fileExt ) ) filePath=self.path + "\\" + file_name + '.' + fileExt.lower() with open(filePath, writeModus) as fd: #contentType='application/application/vnd.ogc.se_xml' if writeModus== 'wb': #binary fd.write( result.content ) else: # string fd.write( result.text ) fd.close() feedback.pushInfo('File saved: ' + str( filePath ) ) if httpStatusCode == 200 and isImage==True: #create World File #first and last Character of image extension wldExt='' try: wldExt = fileExt[0] + fileExt[-1] + 'w' except: wldExt='wld' filePathWld=self.path + "\\" + file_name + '.' + wldExt.lower() outWld = open( filePathWld , 'w') outWld.write( str( pixelSize ) ) outWld.write( '\n' + str(0) ) outWld.write( '\n' + str(0) ) outWld.write( '\n' + str( - pixelSize ) ) outWld.write( '\n' + str( xMin + pixelSize/2 ) ) #Nimm Zentrum des linken oberen Pixels outWld.write( '\n' + str( yMax - pixelSize/2) ) outWld.close() feedback.pushInfo('WorldFile saved: ' + str( filePathWld ) ) else: hasError=True feedback.pushInfo("Problem at Feature " + str(i) + "(" + file_name + "): " + unicode(url) + " HTTP Status Code:" + str(httpStatusCode) ) else: # Result is empty feedback.pushInfo(".. trials without Result: " + str(result)) loopText = loopText + " Result is empty for (" + file_name + "): " + unicode(url) + "\n WMS-Server delivers no data to create a file. " + str(numTrials) + ' trials were excecuted.' except Exception as err: hasError=True loopText = loopText + "\n\t Download error: On Feature " + str(i) + "(" + file_name + "): " + " URL: " + unicode(url) + " " + str(err.args) + ";" + str(repr(err) + "\n HTTP Status Code: " + str( httpStatusCode ) + '\n Content-Type: ' + str(contentType) ) attrs.append( bboxText ) attrs.append( url ) attrs.append( self.path + "\\") attrs.append( file_name + '.' + fileExt.lower() ) attrs.append( contentType ) attrs.append( httpStatusCode ) try: out_feat = QgsFeature() #check if source is a spatial layer if vectorLayer.wkbType() != 100: out_feat.setGeometry(geom) out_feat.setAttributes(attrs) # Add a feature in the sink sink.addFeature(out_feat, QgsFeatureSink.FastInsert) except IOError as e: hasError=True loopText = loopText + "\n\t" + "DP I/O error({0}): {1}".format(e.errno, e.strerror) except ValueError: hasError=True loopText = loopText + "\n\t" + "Value-Error" except QgsError as qGisErr: hasError=True loopText = loopText + "\n\t" + " QgsError: " + qGisErr.message() loopText = loopText + "\n\t" + qGisErr.summary() except Exception as err: hasError=True loopText = loopText + "\n\t Error while taking over the feature " + str(i) + "(" + file_name + "): " +" "+ str(err.args) + ";" + str(repr(err)) pass feedback.setProgress( int( i * total ) ) if hasError==True: feedback.pushInfo(loopText) else: countDownload=countDownload+1 msgInfo=self.tr( str( countDownload ) + " Downloads from " + str(i+1) + " Features to Directory " + self.path) feedback.pushInfo(msgInfo) # Return the results of the algorithm. In this case our only result is return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): t_regard = self.parameterAsSource(parameters, self.MANHOLES_TABLE, context) g_regard = self.parameterAsSource(parameters, self.GEOM_MANHOLES, context) t_troncon = self.parameterAsVectorLayer(parameters, self.SEGMENTS_TABLE, context) g_troncon = self.parameterAsVectorLayer(parameters, self.GEOM_SEGMENTS, context) exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope( QgsExpressionContextUtils.projectScope(context.project())) exp_context.appendScope( QgsExpressionContextUtils.layerScope(t_troncon)) exp_str = '"id_geom_troncon" IS NULL' exp = QgsExpression(exp_str) exp.prepare(exp_context) if exp.hasEvalError(): raise QgsProcessingException( tr('* ERROR: Expression {} has eval error: {}').format( exp.expression(), exp.evalErrorString())) request = QgsFeatureRequest(exp, exp_context) r_ids = [] # identifiant des regards l_t_f = {} # lien troncon fichier l_f_t = {} # lien fichier troncons troncons = {} sorties = {} entrees = {} for tro in t_troncon.getFeatures(request): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} segment_number = tro['id'] r1 = tro['id_regard1'] r2 = tro['id_regard2'] if r1 not in r_ids: r_ids.append(r1) if r2 not in r_ids: r_ids.append(r2) fid = tro['id_file'] l_t_f[segment_number] = fid if fid in l_f_t: l_f_t[fid] = l_f_t[fid] + [segment_number] else: l_f_t[fid] = [segment_number] troncons[segment_number] = (r1, r2) if r1 in sorties: sorties[r1] = sorties[r1] + [segment_number] else: sorties[r1] = [segment_number] if r2 in entrees: entrees[r2] = entrees[r2] + [segment_number] else: entrees[r2] = [segment_number] exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope( QgsExpressionContextUtils.projectScope(context.project())) exp_context.appendScope(t_regard.createExpressionContextScope()) exp_str = ('"id_geom_regard" IS NOT NULL ' 'AND ' '"id" IN ({})').format(','.join([str(i) for i in r_ids] + ['-1'])) exp = QgsExpression(exp_str) exp.prepare(exp_context) if exp.hasEvalError(): raise QgsProcessingException( tr('* ERROR: Expression {} has eval error: {}').format( exp.expression(), exp.evalErrorString())) request = QgsFeatureRequest(exp, exp_context) g_ids = [] # identifiants des géométrie de regards l_r_g = {} # lien regard geometrie l_g_r = {} # lien geometrie regards for reg in t_regard.getFeatures(request): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} segment_number = reg['id'] fid = reg['id_file'] if segment_number in sorties: sorties[segment_number] = [ trid for trid in sorties[segment_number] if l_t_f[trid] == fid ] if segment_number in entrees: entrees[segment_number] = [ trid for trid in entrees[segment_number] if l_t_f[trid] == fid ] gid = reg['id_geom_regard'] l_r_g[segment_number] = gid if gid not in g_ids: g_ids.append(gid) if gid in l_g_r: l_g_r[gid] = l_g_r[gid] + [segment_number] else: l_g_r[gid] = [segment_number] exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope( QgsExpressionContextUtils.projectScope(context.project())) exp_context.appendScope(g_regard.createExpressionContextScope()) exp_str = '"id" IN ({})'.format(','.join([str(i) for i in g_ids] + ['-1'])) exp = QgsExpression(exp_str) exp.prepare(exp_context) if exp.hasEvalError(): raise QgsProcessingException( tr('* ERROR: Expression {} has eval error: {}').format( exp.expression(), exp.evalErrorString())) request = QgsFeatureRequest(exp, exp_context) points = {} pt_labels = {} for reg in g_regard.getFeatures(request): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} segment_number = reg['id'] points[segment_number] = reg.geometry().asPoint() pt_labels[segment_number] = reg['label'] lines = {} for gid in points: # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if gid not in l_g_r: continue for rid in l_g_r[gid]: # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if rid in sorties: for tid in sorties[rid]: # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if tid in lines: continue if tid not in troncons: continue r2 = troncons[tid][1] if r2 not in l_r_g: continue g2 = l_r_g[r2] if g2 not in points: continue lines[tid] = (gid, g2) if rid in entrees: for tid in entrees[rid]: # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if tid in lines: continue if tid not in troncons: continue r1 = troncons[tid][0] if r1 not in l_r_g: continue g1 = l_r_g[r1] if g1 not in points: continue lines[tid] = (g1, gid) # Creation des troncons l_t_g = {} # lien troncon et geometrie troncon l_pts_t = {} # lien points troncons features = [] # les objets de geometrie de troncon geom_point_keys = [] # liste des clés des points troncons for tid, pts in lines.items(): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope( QgsExpressionContextUtils.projectScope(context.project())) exp_context.appendScope( QgsExpressionContextUtils.layerScope(g_troncon)) exp_str = ('"id_geom_regard_amont" = {} AND ' '"id_geom_regard_aval" = {}').format(pts[0], pts[1]) exp = QgsExpression(exp_str) exp.prepare(exp_context) if exp.hasEvalError(): raise QgsProcessingException( tr('* ERROR: Expression {} has eval error: {}').format( exp.expression(), exp.evalErrorString())) request = QgsFeatureRequest(exp, exp_context) request.setLimit(1) for tro in g_troncon.getFeatures(request): l_t_g[tid] = tro['id'] continue if (pts[0], pts[1]) in geom_point_keys: l_pts_t[(pts[0], pts[1])].append(tid) continue else: l_pts_t[(pts[0], pts[1])] = [tid] geom_point_keys.append((pts[0], pts[1])) feat_t = QgsVectorLayerUtils.createFeature(g_troncon) feat_t.setAttribute( 'label', '{}-{}'.format(pt_labels[pts[0]], pt_labels[pts[1]])) feat_t.setAttribute('id_geom_regard_amont', pts[0]) feat_t.setAttribute('id_geom_regard_aval', pts[1]) feat_t.setGeometry( QgsGeometry.fromPolylineXY([points[pts[0]], points[pts[1]]])) features.append(feat_t) # Ajout des objets troncons if features: g_troncon.startEditing() (res, outFeats) = g_troncon.dataProvider().addFeatures(features) if not res or not outFeats: raise QgsProcessingException( tr('* ERREUR: lors de l\'enregistrement ' 'des regards {}').format(', '.join( g_troncon.dataProvider().errors()))) if not g_troncon.commitChanges(): raise QgsProcessingException( tr('* ERROR: Commit {}.').format(g_troncon.commitErrors())) for tro in outFeats: # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if not tro['id']: continue key = (tro['id_geom_regard_amont'], tro['id_geom_regard_aval']) if key not in geom_point_keys: continue for tid in l_pts_t[(pts[0], pts[1])]: l_t_g[tid] = tro['id'] # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} for tid, pts in lines.items(): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if tid in l_t_g: continue exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope( QgsExpressionContextUtils.projectScope(context.project())) exp_context.appendScope( QgsExpressionContextUtils.layerScope(g_troncon)) exp_str = ('"id_geom_regard_amont" = {} AND ' '"id_geom_regard_aval" = {}').format(pts[0], pts[1]) exp = QgsExpression(exp_str) exp.prepare(exp_context) if exp.hasEvalError(): raise QgsProcessingException( tr('* ERROR: Expression {} has eval error: {}').format( exp.expression(), exp.evalErrorString())) request = QgsFeatureRequest(exp, exp_context) request.setLimit(1) for tro in g_troncon.getFeatures(request): l_t_g[tid] = tro['id'] # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} if not l_t_g.keys(): raise QgsProcessingException( tr('* ERREUR: Aucune géométrie de tronçon')) # Mise a jour de la table troncon exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope( QgsExpressionContextUtils.projectScope(context.project())) exp_context.appendScope( QgsExpressionContextUtils.layerScope(t_troncon)) exp_str = ('"id_geom_troncon" IS NULL AND ' 'id IN ({})').format(','.join( [str(i) for i in l_t_g.keys()])) exp = QgsExpression(exp_str) exp.prepare(exp_context) if exp.hasEvalError(): raise QgsProcessingException( tr('* ERROR: Expression %s has eval error: %s').format( exp.expression(), exp.evalErrorString())) # Mise a jour de la table de tronçon request = QgsFeatureRequest(exp, exp_context) t_troncon.startEditing() segment_number = 0 for tro in t_troncon.getFeatures(request): tro.setAttribute('id_geom_troncon', l_t_g[tro['id']]) t_troncon.updateFeature(tro) # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): return {self.SEGMENT_CREATED: None} segment_number += 1 if not t_troncon.commitChanges(): raise QgsProcessingException( tr('* ERROR: Commit %s.') % t_troncon.commitErrors()) # Returns empty dict if no outputs return {self.SEGMENT_CREATED: segment_number}
def processAlgorithm(self, parameters, context, feedback): if DEBUG_MODE: logMessage( "processAlgorithm(): {}".format(self.__class__.__name__), False) clayer = self.parameterAsLayer(parameters, self.INPUT, context) title_field = self.parameterAsString(parameters, self.TITLE_FIELD, context) cf_filter = self.parameterAsBool(parameters, self.CF_FILTER, context) fixed_scale = self.parameterAsEnum(parameters, self.SCALE, context) # == 1 buf = self.parameterAsDouble(parameters, self.BUFFER, context) tex_width = self.parameterAsInt(parameters, self.TEX_WIDTH, context) orig_tex_height = self.parameterAsInt(parameters, self.TEX_HEIGHT, context) header_exp = QgsExpression( self.parameterAsExpression(parameters, self.HEADER, context)) footer_exp = QgsExpression( self.parameterAsExpression(parameters, self.FOOTER, context)) exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.layerScope(clayer)) out_dir = self.parameterAsString(parameters, self.OUTPUT, context) if not QDir(out_dir).exists(): QDir().mkpath(out_dir) if DEBUG_MODE: openDirectory(out_dir) mapSettings = self.controller.settings.mapSettings baseExtent = self.controller.settings.baseExtent rotation = mapSettings.rotation() orig_size = mapSettings.outputSize() if cf_filter: cf_layer = QgsMemoryProviderUtils.createMemoryLayer( "current feature", clayer.fields(), clayer.wkbType(), clayer.crs()) layers = [ cf_layer if lyr == clayer else lyr for lyr in mapSettings.layers() ] mapSettings.setLayers(layers) doc = QDomDocument("qgis") clayer.exportNamedStyle(doc) cf_layer.importNamedStyle(doc) total = clayer.featureCount() for current, feature in enumerate(clayer.getFeatures()): if feedback.isCanceled(): break if cf_filter: cf_layer.startEditing() cf_layer.deleteFeatures( [f.id() for f in cf_layer.getFeatures()]) cf_layer.addFeature(feature) cf_layer.commitChanges() title = feature.attribute(title_field) feedback.setProgressText("({}/{}) Exporting {}...".format( current + 1, total, title)) logMessage("Exporting {}...".format(title), False) # extent geometry = QgsGeometry(feature.geometry()) geometry.transform(self.transform) center = geometry.centroid().asPoint() if fixed_scale or geometry.type() == QgsWkbTypes.PointGeometry: tex_height = orig_tex_height or int( tex_width * orig_size.height() / orig_size.width()) rect = RotatedRect(center, baseExtent.width(), baseExtent.width() * tex_height / tex_width, rotation).scale(1 + buf / 100) else: geometry.rotate(rotation, center) rect = geometry.boundingBox().scaled(1 + buf / 100) center = RotatedRect.rotatePoint(rect.center(), rotation, center) if orig_tex_height: tex_height = orig_tex_height tex_ratio = tex_width / tex_height rect_ratio = rect.width() / rect.height() if tex_ratio > rect_ratio: rect = RotatedRect(center, rect.height() * tex_ratio, rect.height(), rotation) else: rect = RotatedRect(center, rect.width(), rect.width() / tex_ratio, rotation) else: # fit to buffered geometry bounding box rect = RotatedRect(center, rect.width(), rect.height(), rotation) tex_height = tex_width * rect.height() / rect.width() rect.toMapSettings(mapSettings) mapSettings.setOutputSize(QSize(tex_width, tex_height)) self.controller.settings.setMapSettings(mapSettings) # labels exp_context.setFeature(feature) self.controller.settings.setHeaderLabel( header_exp.evaluate(exp_context)) self.controller.settings.setFooterLabel( footer_exp.evaluate(exp_context)) self.export(title, out_dir, feedback) feedback.setProgress(int(current / total * 100)) if P_OPEN_DIRECTORY and not DEBUG_MODE: openDirectory(out_dir) return {}
def print_atlas(project, layout_name, feature_filter, scales=None, scale=None): """Generate an atlas. :param project: The project to render as atlas. :type project: QgsProject :param layout_name: Name of the layout. :type layout_name: basestring :param feature_filter: QGIS Expression to use to select the feature. It can return many features, a multiple pages PDF will be returned. :type feature_filter: basestring :param scale: A scale to force in the atlas context. Default to None. :type scale: int :param scales: A list of predefined list of scales to force in the atlas context. Default to None. :type scales: list :return: Path to the PDF. :rtype: basestring """ canvas = QgsMapCanvas() bridge = QgsLayerTreeMapCanvasBridge(project.layerTreeRoot(), canvas) bridge.setCanvasLayers() manager = project.layoutManager() master_layout = manager.layoutByName(layout_name) if not master_layout: raise AtlasPrintException('Layout not found') if master_layout.layoutType() != QgsMasterLayoutInterface.PrintLayout: raise AtlasPrintException('The layout is not a print layout') for l in manager.printLayouts(): if l.name() == layout_name: layout = l break else: raise AtlasPrintException('The layout is not found') atlas = layout.atlas() if not atlas.enabled(): raise AtlasPrintException('The layout is not enabled for an atlas') settings = QgsLayoutExporter.PdfExportSettings() if scale: layout.referenceMap().setAtlasScalingMode(QgsLayoutItemMap.Fixed) layout.referenceMap().setScale(scale) if scales: layout.referenceMap().setAtlasScalingMode(QgsLayoutItemMap.Predefined) if Qgis.QGIS_VERSION_INT >= 30900: settings.predefinedMapScales = scales else: layout.reportContext().setPredefinedScales(scales) layer = atlas.coverageLayer() feature_filter = optimize_expression(layer, feature_filter) expression = QgsExpression(feature_filter) if expression.hasParserError(): raise AtlasPrintException( 'Expression is invalid, parser error: {}'.format( expression.parserErrorString())) context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope(project)) context.appendScope(QgsExpressionContextUtils.layoutScope(layout)) context.appendScope(QgsExpressionContextUtils.atlasScope(atlas)) context.appendScope(QgsExpressionContextUtils.layerScope(layer)) expression.prepare(context) if expression.hasEvalError(): raise AtlasPrintException( 'Expression is invalid, eval error: {}'.format( expression.evalErrorString())) atlas.setFilterFeatures(True) atlas.setFilterExpression(feature_filter) if not scales and layout.referenceMap().atlasScalingMode( ) == QgsLayoutItemMap.Predefined: if Qgis.QGIS_VERSION_INT >= 30900: use_project = project.useProjectScales() map_scales = project.mapScales() else: map_scales = project_scales(project) use_project = len(map_scales) == 0 if not use_project or len(map_scales) == 0: QgsMessageLog.logMessage( 'Map scales not found in project, fetching predefined map scales in global config', 'atlasprint', Qgis.Info) map_scales = global_scales() if Qgis.QGIS_VERSION_INT >= 30900: settings.predefinedMapScales = map_scales else: layout.reportContext().setPredefinedScales(map_scales) export_path = os.path.join(tempfile.gettempdir(), '{}_{}.pdf'.format(layout_name, uuid4())) exporter = QgsLayoutExporter(layout) result = exporter.exportToPdf(atlas, export_path, settings) if result[0] != QgsLayoutExporter.Success and not os.path.isfile( export_path): raise Exception('export not generated {}'.format(export_path)) return export_path
def processAlgorithm(self, parameters, context, feedback): self.parameters = parameters self.context = context self.feedback = feedback filename = self.parameterAsFileOutput(parameters, self.PrmOutputKmz, context) source = self.parameterAsSource(parameters, self.PrmInputLayer, context) # Before we go further check to make sure we have a valid vector layer wkbtype = source.wkbType() geomtype = QgsWkbTypes.geometryType(wkbtype) if geomtype == QgsWkbTypes.UnknownGeometry or geomtype == QgsWkbTypes.NullGeometry: raise QgsProcessingException( 'Algorithm input is not a valid point, line, or polygon layer.' ) layer = self.parameterAsLayer(parameters, self.PrmInputLayer, context) if self.PrmNameField not in parameters or parameters[ self.PrmNameField] is None: name_field = None else: name_field = self.parameterAsString(parameters, self.PrmNameField, context) desc_fields = self.parameterAsFields(parameters, self.PrmDescriptionField, context) desc_cnt = len(desc_fields) export_style = self.parameterAsInt(parameters, self.PrmExportStyle, context) if self.PrmUseGoogleIcon not in parameters or parameters[ self.PrmUseGoogleIcon] is None: google_icon = None else: google_icon = self.parameterAsEnum(parameters, self.PrmUseGoogleIcon, context) self.line_width_factor = self.parameterAsDouble( parameters, self.PrmLineWidthFactor, context) alt_interpret = self.parameterAsEnum(parameters, self.PrmAltitudeInterpretation, context) if self.PrmAltitudeMode not in parameters or parameters[ self.PrmAltitudeMode] is None: default_alt_mode = None else: default_alt_mode = ALTITUDE_MODES[self.parameterAsEnum( parameters, self.PrmAltitudeMode, context)] alt_mode_field = self.parameterAsString(parameters, self.PrmAltitudeModeField, context) altitude_field = self.parameterAsString(parameters, self.PrmAltitudeField, context) altitude_addend = self.parameterAsDouble(parameters, self.PrmAltitudeAddend, context) date_time_stamp_field = self.parameterAsString( parameters, self.PrmDateTimeStampField, context) date_stamp_field = self.parameterAsString(parameters, self.PrmDateStampField, context) time_stamp_field = self.parameterAsString(parameters, self.PrmTimeStampField, context) date_time_begin_field = self.parameterAsString( parameters, self.PrmDateTimeBeginField, context) date_begin_field = self.parameterAsString(parameters, self.PrmDateBeginField, context) time_begin_field = self.parameterAsString(parameters, self.PrmTimeBeginField, context) date_time_end_field = self.parameterAsString(parameters, self.PrmDateTimeEndField, context) date_end_field = self.parameterAsString(parameters, self.PrmDateEndField, context) time_end_field = self.parameterAsString(parameters, self.PrmTimeEndField, context) if self.PrmPhotoField not in parameters or parameters[ self.PrmPhotoField] is None: photo_path_field = None else: photo_path_field = self.parameterAsString(parameters, self.PrmPhotoField, context) self.photos = {} hasz = QgsWkbTypes.hasZ(wkbtype) if alt_interpret == 0: hasz = False default_alt_mode = None alt_mode_field = None altitude_field = None elif alt_interpret == 2: hasz = False src_crs = source.sourceCrs() if src_crs != self.epsg4326: geomTo4326 = QgsCoordinateTransform(src_crs, self.epsg4326, QgsProject.instance()) self.symcontext = QgsRenderContext.fromMapSettings( settings.canvas.mapSettings()) self.png_icons = [] self.cat_styles = {} kml = simplekml.Kml() kml.resetidcounter() if layer: try: self.render = layer.renderer() self.exp_context = QgsExpressionContext() self.exp_context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) except Exception: if export_style: export_style = 0 feedback.reportError( 'Layer style cannot be determined. Processing will continue without symbol style export.' ) else: if export_style: feedback.reportError( 'There appears to be a valid source, but not a valid layer style. Processing will continue without symbol style export.' ) export_style = 0 if export_style: render_type = self.render.type() if render_type == 'singleSymbol': export_style = 1 elif render_type == 'categorizedSymbol': style_field = self.render.classAttribute() self.field_exp = QgsExpression(style_field) export_style = 2 elif render_type == 'graduatedSymbol': style_field = self.render.classAttribute() self.field_exp = QgsExpression(style_field) export_style = 3 else: feedback.reportError( 'Only single, categorized, and graduated symbol styles can be exported. Processing will continue without symbol style export.' ) export_style = 0 if export_style: self.initStyles(export_style, google_icon, name_field, geomtype, kml) folder = kml.newfolder(name=source.sourceName()) altitude = 0 featureCount = source.featureCount() total = 100.0 / featureCount if featureCount else 0 num_features = 0 iterator = source.getFeatures() for cnt, feature in enumerate(iterator): if feedback.isCanceled(): break num_features += 1 if altitude_field: try: altitude = float(feature[altitude_field]) except Exception: altitude = 0 geom = feature.geometry() if src_crs != self.epsg4326: geom.transform(geomTo4326) if geom.isMultipart() or (name_field and geomtype == QgsWkbTypes.PolygonGeometry): kmlgeom = folder.newmultigeometry() kml_item = kmlgeom else: kmlgeom = folder kml_item = None if geomtype == QgsWkbTypes.PointGeometry: # POINTS for pt in geom.parts(): kmlpart = kmlgeom.newpoint() self.setAltitudeMode(kmlpart, feature, default_alt_mode, alt_mode_field) if kml_item is None: kml_item = kmlpart if hasz: kmlpart.coords = [(pt.x(), pt.y(), pt.z() + altitude_addend)] else: kmlpart.coords = [(pt.x(), pt.y(), altitude + altitude_addend)] elif geomtype == QgsWkbTypes.LineGeometry: # LINES for part in geom.parts(): kmlpart = kmlgeom.newlinestring() self.setAltitudeMode(kmlpart, feature, default_alt_mode, alt_mode_field) if kml_item is None: kml_item = kmlpart if hasz: kmlpart.coords = [(pt.x(), pt.y(), pt.z() + altitude_addend) for pt in part] else: kmlpart.coords = [(pt.x(), pt.y(), altitude + altitude_addend) for pt in part] elif geomtype == QgsWkbTypes.PolygonGeometry: # POLYGONS if name_field: centroid = geom.centroid().asPoint() name = '{}'.format(feature[name_field]) labelpart = kmlgeom.newpoint(coords=[(centroid.x(), centroid.y())], name=name) for part in geom.parts(): kmlpart = kmlgeom.newpolygon() self.setAltitudeMode(kmlpart, feature, default_alt_mode, alt_mode_field) if kml_item is None: kml_item = kmlpart num_interior_rings = part.numInteriorRings() ext_ring = part.exteriorRing() if hasz: kmlpart.outerboundaryis = [(pt.x(), pt.y(), pt.z() + altitude_addend) for pt in ext_ring] else: kmlpart.outerboundaryis = [(pt.x(), pt.y(), altitude + altitude_addend) for pt in ext_ring] if num_interior_rings: ib = [] for i in range(num_interior_rings): if hasz: ib.append([(pt.x(), pt.y(), pt.z() + altitude_addend) for pt in part.interiorRing(i)]) else: ib.append([(pt.x(), pt.y(), altitude + altitude_addend) for pt in part.interiorRing(i)]) kmlpart.innerboundaryis = ib self.exportStyle(kml_item, feature, export_style, geomtype) if name_field: self.exportName(kml_item, feature[name_field]) if photo_path_field: photo_path = feature[photo_path_field].strip() if os.path.exists(photo_path): if not (photo_path in self.photos): local_path = kml.addfile(photo_path) self.photos[photo_path] = local_path else: photo_path = None else: photo_path = None if desc_cnt == 1: self.exportDescription(kml_item, feature[desc_fields[0]], photo_path) elif desc_cnt > 1: self.exportFields(kml_item, desc_fields, feature, photo_path) # Process the first date / time fields date_time_str = self.parseDateTimeValues(feature, date_time_stamp_field, date_stamp_field, time_stamp_field) if date_time_str: kml_item.timestamp.when = date_time_str date_time_str = self.parseDateTimeValues(feature, date_time_begin_field, date_begin_field, time_begin_field) if date_time_str: kml_item.timespan.begin = date_time_str date_time_str = self.parseDateTimeValues(feature, date_time_end_field, date_end_field, time_end_field) if date_time_str: kml_item.timespan.end = date_time_str if cnt % 100 == 0: feedback.setProgress(int(cnt * total)) if num_features == 0: feedback.pushInfo('No features processed') else: kml.savekmz(filename) self.cleanup() 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 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 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 runGetFeatureTests(self, provider): assert len([f for f in provider.getFeatures()]) == 5 self.assert_query(provider, 'name ILIKE \'QGIS\'', []) self.assert_query(provider, '"name" IS NULL', [5]) self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4]) self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4]) self.assert_query(provider, 'name = \'Apple\'', [2]) self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'name = \'apple\'', []) self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4]) self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4]) self.assert_query(provider, 'name LIKE \'Apple\'', [2]) self.assert_query(provider, 'name LIKE \'aPple\'', []) self.assert_query(provider, 'name ILIKE \'aPple\'', [2]) self.assert_query(provider, 'name ILIKE \'%pp%\'', [2]) self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4]) self.assert_query(provider, '-cnt > 0', [5]) self.assert_query(provider, 'cnt < 0', [5]) self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4]) self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4]) self.assert_query(provider, 'cnt <= 100', [1, 5]) self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4]) self.assert_query(provider, 'cnt = 50 * 2', [1]) self.assert_query(provider, 'cnt = 99 + 1', [1]) self.assert_query(provider, 'cnt = 101 - 1', [1]) self.assert_query(provider, 'cnt - 1 = 99', [1]) self.assert_query(provider, '-cnt - 1 = -101', [1]) self.assert_query(provider, '-(-cnt) = 100', [1]) self.assert_query(provider, '-(cnt) = -(100)', [1]) self.assert_query(provider, 'cnt + 1 = 101', [1]) self.assert_query(provider, 'cnt = 1100 % 1000', [1]) self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1]) self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1]) self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4]) self.assert_query(provider, '\'x\' || "name" IS NULL', [5]) self.assert_query(provider, 'cnt = 10 ^ 2', [1]) self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1]) self.assert_query( provider, '"name"="name2"', [2, 4]) # mix of matched and non-matched case sensitive names self.assert_query(provider, 'true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false', []) # Three value logic self.assert_query(provider, 'false and false', []) self.assert_query(provider, 'false and true', []) self.assert_query(provider, 'false and NULL', []) self.assert_query(provider, 'true and false', []) self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true and NULL', []) self.assert_query(provider, 'NULL and false', []) self.assert_query(provider, 'NULL and true', []) self.assert_query(provider, 'NULL and NULL', []) self.assert_query(provider, 'false or false', []) self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'false or NULL', []) self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or false', []) self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5]) self.assert_query(provider, 'NULL or NULL', []) self.assert_query(provider, 'not true', []) self.assert_query(provider, 'not false', [1, 2, 3, 4, 5]) self.assert_query(provider, 'not null', []) # not self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4]) self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4]) self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3]) self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5]) # type conversion - QGIS expressions do not mind that we are comparing a string # against numeric literals self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5]) # geometry self.assert_query( provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2]) # combination of an uncompilable expression and limit feature = next(self.vl.getFeatures('pk=4')) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('parent', feature) context.appendScope(scope) request = QgsFeatureRequest() request.setExpressionContext(context) request.setFilterExpression('"pk" = attribute(@parent, \'pk\')') request.setLimit(1) values = [f['pk'] for f in self.vl.getFeatures(request)] self.assertEqual(values, [4])
def processAlgorithm(self, progress): layer = self.getParameterValue(self.INPUT_LAYER) mapping = self.getParameterValue(self.FIELDS_MAPPING) output = self.getOutputFromName(self.OUTPUT_LAYER) layer = dataobjects.getObjectFromUri(layer) fields = [] expressions = [] da = QgsDistanceArea() da.setSourceCrs(layer.crs().srsid()) da.setEllipsoidalMode( iface.mapCanvas().mapSettings().hasCrsTransformEnabled()) da.setEllipsoid(QgsProject.instance().readEntry( 'Measure', '/Ellipsoid', GEO_NONE)[0]) exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope()) exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer)) for field_def in mapping: fields.append( QgsField(name=field_def['name'], type=field_def['type'], len=field_def['length'], prec=field_def['precision'])) expression = QgsExpression(field_def['expression']) expression.setGeomCalculator(da) expression.setDistanceUnits(QgsProject.instance().distanceUnits()) expression.setAreaUnits(QgsProject.instance().areaUnits()) if expression.hasParserError(): raise GeoAlgorithmExecutionException( self.tr(u'Parser error in expression "{}": {}').format( str(field_def['expression']), str(expression.parserErrorString()))) expression.prepare(exp_context) if expression.hasEvalError(): raise GeoAlgorithmExecutionException( self.tr(u'Evaluation error in expression "{}": {}').format( str(field_def['expression']), str(expression.evalErrorString()))) expressions.append(expression) writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs()) # Create output vector layer with new attributes error = '' calculationSuccess = True inFeat = QgsFeature() outFeat = QgsFeature() features = vector.features(layer) total = 100.0 / len(features) for current, inFeat in enumerate(features): rownum = current + 1 geometry = inFeat.geometry() outFeat.setGeometry(geometry) attrs = [] for i in range(0, len(mapping)): field_def = mapping[i] expression = expressions[i] exp_context.setFeature(inFeat) exp_context.lastScope().setVariable("row_number", rownum) value = expression.evaluate(exp_context) if expression.hasEvalError(): calculationSuccess = False error = expression.evalErrorString() break attrs.append(value) outFeat.setAttributes(attrs) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) del writer if not calculationSuccess: raise GeoAlgorithmExecutionException( self.tr('An error occurred while evaluating the calculation' ' string:\n') + error)
def processAlgorithm(self, parameters, context, feedback): # Get input vector layer vlayer = self.parameterAsVectorLayer(parameters, self.INPUT_LAYER, context) self.layer = vlayer symbol_level = parameters[self.SYMBOL_LEVEL] self.symbol_level = symbol_level self.feedback = feedback color_field = parameters[self.COLOR_FIELD] virtual_color_field = parameters[self.VIRTUAL_COLOR_FIELD] label_field = parameters[self.LABEL_FIELD] virtual_label_field = parameters[self.VIRTUAL_LABEL_FIELD] self.feedback.pushInfo('Layer is {}'.format(vlayer.name())) # Compute symbol based expressions color_expression = self.getColorExpressionFromSymbology() label_expression = self.getLabelExpressionFromSymbology() if not color_expression or not label_expression: raise QgsProcessingException( self. tr('Color expression or label expression cannot be generated')) # Modify features # Start an undo block if color_field and label_field: # Compute the number of steps to display within the progress bar and # get features from source total = 100.0 / vlayer.featureCount() if vlayer.featureCount( ) else 0 # prepare expression exp_context = QgsExpressionContext() exp_context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(vlayer)) # color_expression = "concat('hop ', pcolor)" color_exp = QgsExpression(color_expression) color_idx = vlayer.fields().indexFromName(color_field) label_exp = QgsExpression(label_expression) label_idx = vlayer.fields().indexFromName(label_field) vlayer.beginEditCommand('Translating all features') features = vlayer.getFeatures(QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry)) # .setSubsetOfAttributes([color_idx, label_idx]) ) for current, feature in enumerate(features): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break # Edit feature exp_context.setFeature(feature) # color color_val = color_exp.evaluate(exp_context) self.feedback.pushInfo(color_val) vlayer.changeAttributeValue(feature.id(), color_idx, color_val) # label label_val = label_exp.evaluate(exp_context) self.feedback.pushInfo(label_val) vlayer.changeAttributeValue(feature.id(), label_idx, label_val) # Update the progress bar feedback.setProgress(int(current * total)) if virtual_color_field: self.createOrUpdateLayerExpressionField(virtual_color_field, color_expression) if virtual_label_field: self.createOrUpdateLayerExpressionField(virtual_label_field, label_expression) # End the undo block vlayer.endEditCommand() return {self.OUTPUT: 'ok'}
def testExpression(self): """ test aggregate calculation using an expression """ # numeric layer = QgsVectorLayer("Point?field=fldint:integer", "layer", "memory") pr = layer.dataProvider() int_values = [4, 2, 3, 2, 5, None, 8] features = [] for v in int_values: f = QgsFeature() f.setFields(layer.fields()) f.setAttributes([v]) features.append(f) assert pr.addFeatures(features) #int agg = QgsAggregateCalculator(layer) val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint * 2') self.assertTrue(ok) self.assertEqual(val, 48) # double val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint * 1.5') self.assertTrue(ok) self.assertEqual(val, 36) # datetime val, ok = agg.calculate( QgsAggregateCalculator.Max, "to_date('2012-05-04') + to_interval( fldint || ' day' )") self.assertTrue(ok) self.assertEqual(val, QDateTime(QDate(2012, 5, 12), QTime(0, 0, 0))) # date val, ok = agg.calculate( QgsAggregateCalculator.Min, "to_date(to_date('2012-05-04') + to_interval( fldint || ' day' ))") self.assertTrue(ok) self.assertEqual(val, QDateTime(QDate(2012, 5, 6), QTime(0, 0, 0))) # string val, ok = agg.calculate(QgsAggregateCalculator.Max, "fldint || ' oranges'") self.assertTrue(ok) self.assertEqual(val, '8 oranges') # geometry val, ok = agg.calculate(QgsAggregateCalculator.GeometryCollect, "make_point( coalesce(fldint,0), 2 )") self.assertTrue(ok) self.assertTrue(val.exportToWkt(), 'MultiPoint((4 2, 2 2, 3 2, 2 2,5 2, 0 2,8 2))') # try a bad expression val, ok = agg.calculate(QgsAggregateCalculator.Max, "not_a_field || ' oranges'") self.assertFalse(ok) val, ok = agg.calculate(QgsAggregateCalculator.Max, "5+") self.assertFalse(ok) # test expression context # check default context first # should have layer variables: val, ok = agg.calculate(QgsAggregateCalculator.Min, "@layer_name") self.assertTrue(ok) self.assertEqual(val, 'layer') # but not custom variables: val, ok = agg.calculate(QgsAggregateCalculator.Min, "@my_var") self.assertTrue(ok) self.assertEqual(val, NULL) # test with manual expression context scope = QgsExpressionContextScope() scope.setVariable('my_var', 5) context = QgsExpressionContext() context.appendScope(scope) val, ok = agg.calculate(QgsAggregateCalculator.Min, "@my_var", context) self.assertTrue(ok) self.assertEqual(val, 5)
def fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() self.settings.data_defined_properties.prepare(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.visible_features_only and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.visible_features_only and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR): default_value = QColor(self.settings.properties['in_color']) value, _ = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) colors.append(value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR): default_value = QColor(self.settings.properties['out_color']) value, _ = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) stroke_colors.append(value.name()) self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
def fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() # add a new scope corresponding to the source layer -- this will potentially overwrite any other # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure # that source layer fields and attributes are present in the context context.appendScope( self.source_layer.createExpressionContextScope()) self.settings.data_defined_properties.prepare(context) self.fetch_layout_properties(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.visible_features_only and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.visible_features_only and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) # Some plot types don't draw individual glyphs for each feature, but aggregate them instead. # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each # feature. Instead, the evaluation should be executed only once for these settings. aggregating = self.settings.plot_type in ['box', 'histogram'] executed = False xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['in_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead colors.append(default_value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['out_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color stroke_colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_STROKE_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] stroke_colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead stroke_colors.append(default_value.name()) executed = True self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
import time from qgis.core import QgsExpression, QgsExpressionContext, QgsProject POLY_LYR = "test_poly" EXPRESSION = """ collect_geometries( array_foreach( generate_series(1, num_points($geometry)), make_line( centroid($geometry), point_n($geometry, @element) ) ) ) """ lyr_polys = QgsProject.instance().mapLayersByName(POLY_LYR)[0] t0 = time.time() for counter, poly in enumerate(lyr_polys.getFeatures()): exp = QgsExpression(EXPRESSION) context = QgsExpressionContext() context.setFeature(poly) star = exp.evaluate(context) t1 = time.time() print(f"EXPRESSIONS: run {counter + 1} times for {t1-t0:.2f} secs")