def expression_iterator(self, layer, expression, geometryStorage): featReq = QgsFeatureRequest() expression = QgsExpression(expression) context = QgsExpressionContext() self.stopLoop = False i = 0 for f in layer.getFeatures(featReq): QCoreApplication.processEvents() if self.stopLoop: break self.recordingSearchProgress.emit(i) i += 1 context.setFeature(f) evaluated = unicode(expression.evaluate(context)) if expression.hasEvalError(): continue if f.geometry() is None or f.geometry().centroid() is None: continue centroid = f.geometry().centroid().asPoint() if geometryStorage == 'wkb': geom = binascii.b2a_hex(f.geometry().asWkb()) elif geometryStorage == 'wkt': geom = f.geometry().exportToWkt() elif geometryStorage == 'extent': geom = f.geometry().boundingBox().asWktPolygon() yield ( evaluated, centroid.x(), centroid.y(), geom )
def update_variables_gui(self): variables_scope = QgsExpressionContextScope(self.tr('Model Variables')) for k, v in self.model.variables().items(): variables_scope.setVariable(k, v) variables_context = QgsExpressionContext() variables_context.appendScope(variables_scope) self.variables_editor.setContext(variables_context) self.variables_editor.setEditableScopeIndex(0)
def processAlgorithm(self, feedback): layer = dataobjects.getLayerFromString( self.getParameterValue(self.INPUT_LAYER)) geometry_type = self.getParameterValue(self.OUTPUT_GEOMETRY) wkb_type = None if geometry_type == 0: wkb_type = QgsWkbTypes.Polygon elif geometry_type == 1: wkb_type = QgsWkbTypes.LineString else: wkb_type = QgsWkbTypes.Point if self.getParameterValue(self.WITH_Z): wkb_type = QgsWkbTypes.addZ(wkb_type) if self.getParameterValue(self.WITH_M): wkb_type = QgsWkbTypes.addM(wkb_type) writer = self.getOutputFromName( self.OUTPUT_LAYER).getVectorWriter( layer.fields(), wkb_type, layer.crs()) expression = QgsExpression(self.getParameterValue(self.EXPRESSION)) if expression.hasParserError(): raise GeoAlgorithmExecutionException(expression.parserErrorString()) exp_context = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer)) if not expression.prepare(exp_context): raise GeoAlgorithmExecutionException( self.tr('Evaluation error: {0}').format(expression.evalErrorString())) features = vector.features(layer) total = 100.0 / len(features) for current, input_feature in enumerate(features): output_feature = input_feature exp_context.setFeature(input_feature) value = expression.evaluate(exp_context) if expression.hasEvalError(): raise GeoAlgorithmExecutionException( self.tr('Evaluation error: {0}').format(expression.evalErrorString())) if not value: output_feature.setGeometry(QgsGeometry()) else: if not isinstance(value, QgsGeometry): raise GeoAlgorithmExecutionException( self.tr('{} is not a geometry').format(value)) output_feature.setGeometry(value) writer.addFeature(output_feature) feedback.setProgress(int(current * total)) del writer
def check_for_update_events(self, widget, value): if not self.feature: return from qgis.core import QgsExpression, QgsExpressionContext, QgsExpressionContextScope # If we don't have any events for this widgets just get out now if not widget.id in self.events: return events = self.events[widget.id] events = [event for event in events if event['event'].lower() == 'update'] if not events: return feature = self.to_feature(no_defaults=True) for event in events: action = event['action'].lower() targetid = event['target'] if targetid == widget.id: utils.log("Can't connect events to the same widget. ID {}".format(targetid)) continue widget = self.get_widget_from_id(targetid) if not widget: utils.log("Can't find widget for id {} in form".format(targetid)) continue condition = event['condition'] expression = event['value'] context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable("value", value) scope.setVariable("field", widget.field) context.setFeature(feature) context.appendScope(scope) conditionexp = QgsExpression(condition) exp = QgsExpression(expression) if action.lower() == "show": widget.hidden = not conditionexp.evaluate(context) if action.lower() == "hide": widget.hidden = conditionexp.evaluate(context) if action == 'widget expression': if conditionexp.evaluate(context): newvalue = self.widget_default(field, feature=feature) widget.setvalue(newvalue) if action == 'set value': if conditionexp.evaluate(context): newvalue = exp.evaluate(context) widget.setvalue(newvalue)
def evaluateExpression(self, text): context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) exp = QgsExpression(text) if exp.hasParserError(): raise Exception(exp.parserErrorString()) result = exp.evaluate(context) if exp.hasEvalError(): raise ValueError(exp.evalErrorString()) return result
def sum_fields(layer, output_field_key, input_fields): """Sum the value of input_fields and put it as output_field. :param layer: The vector layer. :type layer: QgsVectorLayer :param output_field_key: The output field definition key. :type output_field_key: basestring :param input_fields: List of input fields' name. :type input_fields: list """ field_definition = definition(output_field_key) output_field_name = field_definition['field_name'] # If the fields only has one element if len(input_fields) == 1: # Name is different, copy it if input_fields[0] != output_field_name: to_rename = {input_fields[0]: output_field_name} # We copy only, it will be deleted later. # We can't rename the field, we need to copy it as the same # field might be used many times in the FMT tool. copy_fields(layer, to_rename) else: # Name is same, do nothing return else: # Creating expression # Put field name in a double quote. See #4248 input_fields = ['"%s"' % f for f in input_fields] string_expression = ' + '.join(input_fields) sum_expression = QgsExpression(string_expression) context = QgsExpressionContext() context.setFields(layer.fields()) sum_expression.prepare(context) # Get the output field index output_idx = layer.fields().lookupField(output_field_name) # Output index is not found layer.startEditing() if output_idx == -1: output_field = create_field_from_definition(field_definition) layer.addAttribute(output_field) output_idx = layer.fields().lookupField(output_field_name) # Iterate to all features for feature in layer.getFeatures(): context.setFeature(feature) result = sum_expression.evaluate(context) feature[output_idx] = result layer.updateFeature(feature) layer.commitChanges()
def sum_fields(layer, output_field_key, input_fields): """Sum the value of input_fields and put it as output_field. :param layer: The vector layer. :type layer: QgsVectorLayer :param output_field_key: The output field definition key. :type output_field_key: basestring :param input_fields: List of input fields' name. :type input_fields: list """ field_definition = definition(output_field_key) output_field_name = field_definition['field_name'] # If the fields only has one element if len(input_fields) == 1: # Name is different, copy it if input_fields[0] != output_field_name: copy_fields(layer, { input_fields[0]: output_field_name}) # Name is same, do nothing else: return else: # Creating expression # Put field name in a double quote. See #4248 input_fields = ['"%s"' % f for f in input_fields] string_expression = ' + '.join(input_fields) sum_expression = QgsExpression(string_expression) context = QgsExpressionContext() context.setFields(layer.pendingFields()) sum_expression.prepare(context) # Get the output field index output_idx = layer.fieldNameIndex(output_field_name) # Output index is not found if output_idx == -1: output_field = create_field_from_definition(field_definition) layer.startEditing() layer.addAttribute(output_field) layer.commitChanges() output_idx = layer.fieldNameIndex(output_field_name) layer.startEditing() # Iterate to all features for feature in layer.getFeatures(): context.setFeature(feature) result = sum_expression.evaluate(context) feature[output_idx] = result layer.updateFeature(feature) layer.commitChanges()
def add_flooded_field(self, shapefile_path): """Create the layer from the local shp adding the flooded field. .. versionadded:: 3.3 Use this method to add a calculated field to a shapefile. The shapefile should have a field called 'count' containing the number of flood reports for the field. The field values will be set to 0 if the count field is < 1, otherwise it will be set to 1. :param shapefile_path: Path to the shapefile that will have the flooded field added. :type shapefile_path: basestring :return: A vector layer with the flooded field added. :rtype: QgsVectorLayer """ layer = QgsVectorLayer( shapefile_path, self.tr('Jakarta Floods'), 'ogr') # Add a calculated field indicating if a poly is flooded or not # from qgis.PyQt.QtCore import QVariant layer.startEditing() # Add field with integer from 0 to 4 which represents the flood # class. Its the same as 'state' field except that is being treated # as a string. # This is used for cartography flood_class_field = QgsField('floodclass', QVariant.Int) layer.addAttribute(flood_class_field) layer.commitChanges() layer.startEditing() flood_class_idx = layer.fields().lookupField('floodclass') flood_class_expression = QgsExpression('to_int(state)') context = QgsExpressionContext() context.setFields(layer.fields()) flood_class_expression.prepare(context) # Add field with boolean flag to say if the area is flooded # This is used by the impact function flooded_field = QgsField('flooded', QVariant.Int) layer.dataProvider().addAttributes([flooded_field]) layer.commitChanges() layer.startEditing() flooded_idx = layer.fields().lookupField('flooded') flood_flag_expression = QgsExpression('state > 0') flood_flag_expression.prepare(context) for feature in layer.getFeatures(): context.setFeature(feature) feature[flood_class_idx] = flood_class_expression.evaluate(context) feature[flooded_idx] = flood_flag_expression.evaluate(context) layer.updateFeature(feature) layer.commitChanges() return layer
def test_length_expression(self): # compare length using the ellipsoid in kms and the planimetric distance in meters self.lyr.fieldName = "round($length,5) || ' - ' || round(length($geometry),2)" self.lyr.isExpression = True QgsProject.instance().setCrs(QgsCoordinateReferenceSystem("EPSG:32613")) QgsProject.instance().setEllipsoid("WGS84") QgsProject.instance().setDistanceUnits(QgsUnitTypes.DistanceKilometers) ctxt = QgsExpressionContext() ctxt.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance())) ctxt.appendScope(QgsExpressionContextUtils.layerScope(self.layer)) self._TestMapSettings.setExpressionContext(ctxt) self.lyr.placement = QgsPalLayerSettings.Curved self.lyr.placementFlags = QgsPalLayerSettings.AboveLine | QgsPalLayerSettings.MapOrientation self.checkTest()
def add25dAttributes(cleanLayer, layer, canvas): provider = cleanLayer.dataProvider() provider.addAttributes([QgsField("height", QVariant.Double), QgsField("wallColor", QVariant.String), QgsField("roofColor", QVariant.String)]) cleanLayer.updateFields() fields = cleanLayer.fields() renderer = layer.renderer() renderContext = QgsRenderContext.fromMapSettings(canvas.mapSettings()) feats = layer.getFeatures() context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.layerScope(layer)) expression = QgsExpression('eval(@qgis_25d_height)') heightField = fields.indexFromName("height") wallField = fields.indexFromName("wallColor") roofField = fields.indexFromName("roofColor") renderer.startRender(renderContext, fields) cleanLayer.startEditing() for feat in feats: context.setFeature(feat) height = expression.evaluate(context) if isinstance(renderer, QgsCategorizedSymbolRenderer): classAttribute = renderer.classAttribute() attrValue = feat.attribute(classAttribute) catIndex = renderer.categoryIndexForValue(attrValue) categories = renderer.categories() symbol = categories[catIndex].symbol() elif isinstance(renderer, QgsGraduatedSymbolRenderer): classAttribute = renderer.classAttribute() attrValue = feat.attribute(classAttribute) ranges = renderer.ranges() for range in ranges: if (attrValue >= range.lowerValue() and attrValue <= range.upperValue()): symbol = range.symbol().clone() else: symbol = renderer.symbolForFeature(feat, renderContext) sl1 = symbol.symbolLayer(1) sl2 = symbol.symbolLayer(2) wallColor = sl1.subSymbol().color().name() roofColor = sl2.subSymbol().color().name() provider.changeAttributeValues({feat.id() + 1: {heightField: height, wallField: wallColor, roofField: roofColor}}) cleanLayer.commitChanges() renderer.stopRender(renderContext)
def runGetFeatureTests(self, source): FeatureSourceTestCase.runGetFeatureTests(self, source) # combination of an uncompilable expression and limit feature = next(self.vl.getFeatures('pk=4')) context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('parent', feature) context.appendScope(scope) request = QgsFeatureRequest() request.setExpressionContext(context) request.setFilterExpression('"pk" = attribute(@parent, \'pk\')') request.setLimit(1) values = [f['pk'] for f in self.vl.getFeatures(request)] self.assertEqual(values, [4])
def processAlgorithm(self, feedback): ns = {} ns['feedback'] = feedback ns['scriptDescriptionFile'] = self.descriptionFile for param in self.parameters: ns[param.name] = param.value for out in self.outputs: ns[out.name] = out.value variables = re.findall('@[a-zA-Z0-9_]*', self.script) script = 'import processing\n' script += self.script context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance())) for var in variables: varname = var[1:] if context.hasVariable(varname): script = script.replace(var, context.variable(varname)) else: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, 'Cannot find variable: %s' % varname) exec((script), ns) for out in self.outputs: out.setValue(ns[out.name])
def processAlgorithm(self, progress): layer = layer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT)) expression = self.getParameterValue(self.EXPRESSION) qExp = QgsExpression(expression) if qExp.hasParserError(): raise GeoAlgorithmExecutionException(qExp.parserErrorString()) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( layer.fields(), layer.wkbType(), layer.crs()) context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) context.appendScope(QgsExpressionContextUtils.layerScope(layer)) count = layer.featureCount() step = 100.0 / count if count else 1 request = QgsFeatureRequest(qExp, context) for current, f in enumerate(layer.getFeatures(request)): writer.addFeature(f) progress.setPercentage(int(current * step)) del writer
def processAlgorithm(self, parameters, context, feedback): ns = {} ns['feedback'] = feedback ns['scriptDescriptionFile'] = self.descriptionFile ns['context'] = context for param in self.parameterDefinitions(): ns[param.name] = parameters[param.name()] for out in self.outputs: ns[out.name] = out.value variables = re.findall('@[a-zA-Z0-9_]*', self.script) script = 'import processing\n' script += self.script context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance())) for var in variables: varname = var[1:] if context.hasVariable(varname): script = script.replace(var, context.variable(varname)) else: QgsMessageLog.logMessage(self.tr('Cannot find variable: {0}').format(varname), self.tr('Processing'), QgsMessageLog.WARNING) exec((script), ns) for out in self.outputs: out.setValue(ns[out.name])
def processAlgorithm(self, progress): ns = {} ns["progress"] = progress ns["scriptDescriptionFile"] = self.descriptionFile for param in self.parameters: ns[param.name] = param.value for out in self.outputs: ns[out.name] = out.value variables = re.findall("@[a-zA-Z0-9_]*", self.script) script = "import processing\n" script += self.script context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) for var in variables: varname = var[1:] if context.hasVariable(varname): script = script.replace(var, context.variable(varname)) else: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, "Cannot find variable: %s" % varname) exec((script), ns) for out in self.outputs: out.setValue(ns[out.name])
def expressionContext(self): context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) processingScope = QgsExpressionContextScope() for param in self.alg.parameters: processingScope.setVariable("%s_value" % param.name, "") context.appendScope(processingScope) return context
def _expressionContext(alg): context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) processingScope = QgsExpressionContextScope() for param in alg.parameters: processingScope.setVariable('%s_value' % param.name, '') context.appendScope(processingScope) return context
def createExpressionContext(): context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance())) if iface.mapCanvas(): context.appendScope(QgsExpressionContextUtils.mapSettingsScope(iface.mapCanvas().mapSettings())) processingScope = QgsExpressionContextScope() extent = iface.mapCanvas().fullExtent() processingScope.setVariable('fullextent_minx', extent.xMinimum()) processingScope.setVariable('fullextent_miny', extent.yMinimum()) processingScope.setVariable('fullextent_maxx', extent.xMaximum()) processingScope.setVariable('fullextent_maxy', extent.yMaximum()) context.appendScope(processingScope) return context
def testConcurrency(self): """ The connection pool has a maximum of 4 connections defined (+2 spare connections) Make sure that if we exhaust those 4 connections and force another connection it is actually using the spare connections and does not freeze. This situation normally happens when (at least) 4 rendering threads are active in parallel and one requires an expression to be evaluated. """ # Acquire the maximum amount of concurrent connections iterators = list() for i in range(QgsApplication.instance().maxConcurrentConnectionsPerPool()): iterators.append(self.vl.getFeatures()) # Run an expression that will also do a request and should use a spare # connection. It just should not deadlock here. feat = next(iterators[0]) context = QgsExpressionContext() context.setFeature(feat) exp = QgsExpression('get_feature(\'{layer}\', \'pk\', 5)'.format(layer=self.vl.id())) exp.evaluate(context)
def pointPicked(self, event): for b in self.rubberBands: del b self.rubberBands[:] = [] fieldX = self.cmbXField.currentField() fieldY = self.cmbYField.currentField() artist = event.artist indices = event.ind for i in indices: x = self.xData[artist.name][i] y = self.yData[artist.name][i] if isinstance(x, int): expr = '"{}" = {} AND '.format(fieldX, x) elif isinstance(x, float): expr = 'abs("{}" - {}) <= 0.0000001 AND '.format(fieldX, x) elif isinstance(x, (str, unicode)): expr = """"{}" = '{}' AND """.format(fieldX, x) else: expr = """"{}" = '{}' AND """.format(fieldX, x.toString('yyyy-MM-dd')) if isinstance(y, float): expr += 'abs("{}" - {}) <= 0.0000001'.format(fieldY, y) elif isinstance(y, (str, unicode)): expr += """"{}" = '{}'""".format(fieldY, y) else: expr += '"{}" = {}'.format(fieldY, y) layer = self.cmbLayer.currentLayer() expression = QgsExpression(expr) context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) context.appendScope(QgsExpressionContextUtils.mapSettingsScope(self.canvas.mapSettings())) context.appendScope(QgsExpressionContextUtils.layerScope(layer)) request = QgsFeatureRequest(expression, context) for f in layer.getFeatures(request): hl = QgsHighlight(self.canvas, f.geometry(), layer) hl.setColor(QColor(255, 0, 0)) hl.setWidth(2) self.rubberBands.append(hl)
def __init__(self, provider): super(PyFeatureSource, self).__init__() self._provider = provider self._features = provider._features self._expression_context = QgsExpressionContext() self._expression_context.appendScope(QgsExpressionContextUtils.globalScope()) self._expression_context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance())) self._expression_context.setFields(self._provider.fields()) if self._provider.subsetString(): self._subset_expression = QgsExpression(self._provider.subsetString()) self._subset_expression.prepare(self._expression_context) else: self._subset_expression = None
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 doAction(self, layer, uid, feature): if layer.actions().action(uid).name() == 'openFeatureForm': self.plugin.iface.openFeatureForm(layer, feature) else : ctxt = QgsExpressionContext() ctxt.appendScope(QgsExpressionContextUtils.globalScope()) ctxt.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance())) ctxt.appendScope(QgsExpressionContextUtils.mapSettingsScope(self.canvas.mapSettings())) # Add click_x and click_y to context p = self.toLayerCoordinates(layer, self.pos()) QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), 'click_x', p.x()) QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), 'click_y', p.y()) layer.actions().doAction(uid, feature, ctxt)
def expressionContext(self): context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope(QgsExpressionContextUtils.projectScope()) processingScope = QgsExpressionContextScope() layers = dataobjects.getAllLayers() for layer in layers: name = layer.name() processingScope.setVariable('%s_minx' % name, layer.extent().xMinimum()) processingScope.setVariable('%s_miny' % name, layer.extent().yMinimum()) processingScope.setVariable('%s_maxx' % name, layer.extent().xMaximum()) processingScope.setVariable('%s_maxy' % name, layer.extent().yMaximum()) if isinstance(layer, QgsRasterLayer): cellsize = (layer.extent().xMaximum() - layer.extent().xMinimum()) / layer.width() processingScope.setVariable('%s_cellsize' % name, cellsize) layers = dataobjects.getRasterLayers() for layer in layers: for i in range(layer.bandCount()): stats = layer.dataProvider().bandStatistics(i + 1) processingScope.setVariable('%s_band%i_avg' % (name, i + 1), stats.mean) processingScope.setVariable('%s_band%i_stddev' % (name, i + 1), stats.stdDev) processingScope.setVariable('%s_band%i_min' % (name, i + 1), stats.minimumValue) processingScope.setVariable('%s_band%i_max' % (name, i + 1), stats.maximumValue) extent = iface.mapCanvas().extent() processingScope.setVariable('canvasextent_minx', extent.xMinimum()) processingScope.setVariable('canvasextent_miny', extent.yMinimum()) processingScope.setVariable('canvasextent_maxx', extent.xMaximum()) processingScope.setVariable('canvasextent_maxy', extent.yMaximum()) extent = iface.mapCanvas().fullExtent() processingScope.setVariable('fullextent_minx', extent.xMinimum()) processingScope.setVariable('fullextent_miny', extent.yMinimum()) processingScope.setVariable('fullextent_maxx', extent.xMaximum()) processingScope.setVariable('fullextent_maxy', extent.yMaximum()) context.appendScope(processingScope) return context
def setCustomExpression( self ): """ Initialize and show the expression builder dialog """ layer = None if len( self.tblLayers.selectedItems() ) / 3 == 1: # Single layer selected? for item in self.tblLayers.selectedItems(): if item.column() == 1: # It's the layer name item layer = QgsMapLayerRegistry.instance().mapLayer( item.data( Qt.UserRole ) ) if not self.expressionDlg: self.expressionDlg = ExpressionBuilderDialog( self.iface.mainWindow() ) context = QgsExpressionContext() context.appendScope( QgsExpressionContextUtils.globalScope() ) context.appendScope( QgsExpressionContextUtils.projectScope() ) # Initialize dialog with layer-based names and variables if single layer selected if len( self.tblLayers.selectedItems() ) / 3 == 1: context.appendScope( QgsExpressionContextUtils.layerScope( layer ) ) self.expressionDlg.expressionBuilderWidget.setLayer( layer ) self.expressionDlg.expressionBuilderWidget.loadFieldNames() # This block was borrowed from QGIS/python/plugins/processing/algs/qgis/FieldsCalculator.py da = QgsDistanceArea() da.setSourceCrs( layer.crs().srsid() ) da.setEllipsoidalMode( self.iface.mapCanvas().mapSettings().hasCrsTransformEnabled() ) da.setEllipsoid( QgsProject.instance().readEntry( 'Measure', '/Ellipsoid', GEO_NONE )[0] ) self.expressionDlg.expressionBuilderWidget.setGeomCalculator( da ) # If this layer-field is an AutoField, get its expression if self.optExistingField.isChecked(): fieldName = self.cboField.currentText() expression = self.autoFieldManager.getFieldExpression( layer, fieldName ) self.expressionDlg.expressionBuilderWidget.setExpressionText( expression ) self.expressionDlg.expression = expression # To remember it when closing/opening self.expressionDlg.expressionBuilderWidget.setExpressionContext( context ) self.expressionDlg.show()
class PyFeatureSource(QgsAbstractFeatureSource): def __init__(self, provider): super(PyFeatureSource, self).__init__() self._provider = provider self._features = provider._features self._expression_context = QgsExpressionContext() self._expression_context.appendScope( QgsExpressionContextUtils.globalScope()) self._expression_context.appendScope( QgsExpressionContextUtils.projectScope(QgsProject.instance())) self._expression_context.setFields(self._provider.fields()) if self._provider.subsetString(): self._subset_expression = QgsExpression( self._provider.subsetString()) self._subset_expression.prepare(self._expression_context) else: self._subset_expression = None def getFeatures(self, request): return QgsFeatureIterator(PyFeatureIterator(self, request))
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 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 updateLayer(self): self.layer = dataobjects.getObject(self.cmbInputLayer.currentText()) self.builder.setLayer(self.layer) self.builder.loadFieldNames() exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope()) exp_context.appendScope(QgsExpressionContextUtils.layerScope(self.layer)) exp_context.lastScope().setVariable("row_number", 1) exp_context.setHighlightedVariables(["row_number"]) self.builder.setExpressionContext(exp_context) self.populateFields()
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 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): 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 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 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 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
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")
def updateLayer(self): self.layer = dataobjects.getObject(self.cmbInputLayer.currentText()) self.builder.setLayer(self.layer) self.builder.loadFieldNames() exp_context = QgsExpressionContext() exp_context.appendScope(QgsExpressionContextUtils.globalScope()) exp_context.appendScope(QgsExpressionContextUtils.projectScope()) exp_context.appendScope( QgsExpressionContextUtils.layerScope(self.layer)) exp_context.lastScope().setVariable("row_number", 1) exp_context.setHighlightedVariables(["row_number"]) self.builder.setExpressionContext(exp_context) self.populateFields()
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 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 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 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() == QgsMapLayer.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 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
class ExportKmzAlgorithm(QgsProcessingAlgorithm): """ Algorithm to import KML and KMZ files. """ PrmInputLayer = 'InputLayer' PrmOutputKmz = 'OutputKmz' PrmNameField = 'NameField' PrmDescriptionField = 'DescriptionField' PrmExportStyle = 'ExportStyle' PrmUseGoogleIcon = 'UseGoogleIcon' PrmLineWidthFactor = 'LineWidthFactor' PrmAltitudeInterpretation = 'AltitudeInterpretation' PrmAltitudeMode = 'AltitudeMode' PrmAltitudeModeField = 'AltitudeModeField' PrmAltitudeField = 'AltitudeField' PrmAltitudeAddend = 'AltitudeAddend' PrmDateTimeStampField = 'DateTimeStampField' PrmDateStampField = 'DateStampField' PrmTimeStampField = 'TimeStampField' PrmDateTimeBeginField = 'DateTimeBeginField' PrmDateBeginField = 'DateBeginField' PrmTimeBeginField = 'TimeBeginField' PrmDateTimeEndField = 'DateTimeEndField' PrmDateEndField = 'DateEndField' PrmTimeEndField = 'TimeEndField' PrmPhotoField = 'PhotoField' PrmPhotoDir = 'PhotoDir' epsg4326 = QgsCoordinateReferenceSystem("EPSG:4326") temp_dir = tempfile.gettempdir() def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource(self.PrmInputLayer, 'Input layer', [QgsProcessing.TypeVector])) self.addParameter( QgsProcessingParameterField( self.PrmNameField, 'Name/Label field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, defaultValue='name', optional=True)) if Qgis.QGIS_VERSION_INT >= 31200: self.addParameter( QgsProcessingParameterField( self.PrmDescriptionField, 'Description fields', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True, allowMultiple=True, defaultToAllFields=True)) else: self.addParameter( QgsProcessingParameterField( self.PrmDescriptionField, 'Description fields', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True, allowMultiple=True)) self.addParameter( QgsProcessingParameterBoolean( self.PrmExportStyle, 'Export style for single, categorized, and graduated symbols', True, optional=True)) self.google_icons = list(GOOGLE_ICONS.keys()) self.addParameter( QgsProcessingParameterEnum( self.PrmUseGoogleIcon, #'Use QGIS point color & size, but use one of these Google icons.', 'Point Layers: Use the following Google icon but use QGIS icon color and size', options=self.google_icons, optional=True)) self.addParameter( QgsProcessingParameterEnum( self.PrmAltitudeInterpretation, 'Specify whether to include altitude in the KMZ (must be in meters)', options=[ 'Don\'t use altitude', 'Use QGIS geometry Z value if present', 'Use altitude from one of the feature\'s attributes' ], defaultValue=1, optional=True)) self.addParameter( QgsProcessingParameterEnum( self.PrmAltitudeMode, 'Default altitude mode when not obtained from the attribute table', options=ALTITUDE_MODES, defaultValue=0, optional=True)) self.addParameter( QgsProcessingParameterField( self.PrmAltitudeModeField, 'Altitude mode field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.String, defaultValue='alt_mode', optional=True)) self.addParameter( QgsProcessingParameterField( self.PrmAltitudeField, 'Altitude field (value must be in meters)', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, defaultValue='altitude', optional=True)) self.addParameter( QgsProcessingParameterNumber( self.PrmAltitudeAddend, 'Altitude addend (value must be in meters)', type=QgsProcessingParameterNumber.Double, defaultValue=0, optional=True)) self.addParameter( QgsProcessingParameterField( self.PrmDateTimeStampField, 'Date/Time stamp field (see advanced parameters)', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, defaultValue='time_when', optional=True)) self.addParameter( QgsProcessingParameterField( self.PrmDateTimeBeginField, 'Date/Time span begin field (see advanced parameters)', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, defaultValue='time_begin', optional=True)) self.addParameter( QgsProcessingParameterField( self.PrmDateTimeEndField, 'Date/Time span end field (see advanced parameters)', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, defaultValue='time_end', optional=True)) self.addParameter( QgsProcessingParameterField( self.PrmPhotoField, 'Image path/name field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.String, optional=True)) self.addParameter( QgsProcessingParameterFileDestination(self.PrmOutputKmz, 'Output KMZ file', fileFilter='*.kmz')) # Set up Advanced Parameters param = QgsProcessingParameterNumber( self.PrmLineWidthFactor, 'Line width multiplication factor (widths appear smaller in Google Earth)', QgsProcessingParameterNumber.Double, defaultValue=2, minValue=0, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) param = QgsProcessingParameterField( self.PrmDateStampField, 'Date stamp field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) param = QgsProcessingParameterField( self.PrmTimeStampField, 'Time stamp field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) param = QgsProcessingParameterField( self.PrmDateBeginField, 'Date span begin field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) param = QgsProcessingParameterField( self.PrmTimeBeginField, 'Time span begin field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) param = QgsProcessingParameterField( self.PrmDateEndField, 'Date span end field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) param = QgsProcessingParameterField( self.PrmTimeEndField, 'Time span end field', parentLayerParameterName=self.PrmInputLayer, type=QgsProcessingParameterField.Any, optional=True) param.setFlags(param.flags() | QgsProcessingParameterDefinition.FlagAdvanced) self.addParameter(param) 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 exportStyle(self, kml_item, feature, export_style, geomtype): # self.feedback.pushInfo('exportStyle') if export_style == 1: kml_item.style = self.simple_style elif export_style == 2: # Determine the category expression value self.exp_context.setFeature(feature) try: value = self.field_exp.evaluate(self.exp_context) # Which category does feature value fall in catindex = self.render.categoryIndexForValue(value) except Exception: return # If it is outside the category ranges assign it to the 0 index if catindex not in self.cat_styles: catindex = 0 if catindex in self.cat_styles: kml_item.style = self.cat_styles[catindex] elif export_style == 3: # Determine the gradient expression value self.exp_context.setFeature(feature) try: value = self.field_exp.evaluate(self.exp_context) # Which range of the gradient does this value fall in range = self.render.rangeForValue(value) if range is None: minimum = 1e16 maximum = -1e16 for ran in self.render.ranges(): if ran.lowerValue() < minimum: minimum = ran.lowerValue() if ran.upperValue() > maximum: maximum = ran.upperValue() if value > maximum: value = maximum if value < minimum: value = minimum range = self.render.rangeForValue(value) if range is None: self.feedback.pushInfo( 'An error occured in defining the range object') return except Exception: '''s = traceback.format_exc() self.feedback.pushInfo(s)''' return # Get the symbol related to the specified gradient range # For lines and polygons we would use the color and line sizes symbol = range.symbol() opacity = symbol.opacity() if geomtype == QgsWkbTypes.PointGeometry: sym_size = symbol.size(self.symcontext) color = qcolor2kmlcolor(symbol.color()) elif geomtype == QgsWkbTypes.LineGeometry: sym_size = symbol.width() if sym_size == 0: sym_size = 0.5 color = qcolor2kmlcolor(symbol.color()) key = (sym_size, color) else: symbol_layer = symbol.symbolLayer(0) stroke_style = symbol_layer.strokeStyle() if stroke_style == 0: sym_size = 0 else: sym_size = symbol_layer.strokeWidth() color = qcolor2kmlcolor(symbol_layer.color(), opacity) key = (sym_size, color) if key in self.cat_styles: # self.feedback.pushInfo(' catindex in cat_styles') kml_item.style = self.cat_styles[key] # self.feedback.pushInfo(' style {}'.format(kml_item.style)) def initStyles(self, symtype, google_icon, name_field, geomtype, kml): # self.feedback.pushInfo('initStyles type: {}'.format(symtype)) if symtype == 1: # Single Symbol symbol = self.render.symbol() opacity = symbol.opacity() self.simple_style = simplekml.Style() if geomtype == QgsWkbTypes.PointGeometry: sym_size = symbol.size(self.symcontext) if google_icon is None: bounds = symbol.bounds(QPointF(0, 0), self.symcontext) size = bounds.width() if bounds.height() > size: size = bounds.height() size = math.ceil(size * 1.1) path = os.path.join(self.temp_dir, 'icon.png') self.png_icons.append(path) symbol.exportImage(path, "png", QSize(size, size)) kml.addfile(path) self.simple_style.iconstyle.scale = sym_size / 15 self.simple_style.iconstyle.icon.href = 'files/icon.png' else: self.simple_style.iconstyle.scale = sym_size / 10 self.simple_style.iconstyle.icon.href = GOOGLE_ICONS[ self.google_icons[google_icon]] self.simple_style.iconstyle.color = qcolor2kmlcolor( symbol.color()) elif geomtype == QgsWkbTypes.LineGeometry: symbol_width = symbol.width() if symbol_width == 0: symbol_width = 0.5 self.simple_style.linestyle.color = qcolor2kmlcolor( symbol.color(), opacity) self.simple_style.linestyle.width = symbol_width * self.line_width_factor if name_field: self.simple_style.linestyle.gxlabelvisibility = True else: symbol_layer = symbol.symbolLayer(0) stroke_style = symbol_layer.strokeStyle() if stroke_style == 0: stroke_width = 0 else: stroke_width = symbol_layer.strokeWidth() self.simple_style.linestyle.color = qcolor2kmlcolor( symbol_layer.strokeColor(), opacity) self.simple_style.linestyle.width = stroke_width * self.line_width_factor self.simple_style.polystyle.color = qcolor2kmlcolor( symbol_layer.color(), opacity) if name_field: self.simple_style.iconstyle.scale = 0 elif symtype == 2: # Categorized Symbols for idx, category in enumerate(self.render.categories()): cat_style = simplekml.Style() symbol = category.symbol() opacity = symbol.opacity() # self.feedback.pushInfo(' categories idx: {}'.format(idx)) if geomtype == QgsWkbTypes.PointGeometry: # self.feedback.pushInfo(' PointGeometry') sym_size = symbol.size(self.symcontext) # self.feedback.pushInfo('sym_size: {}'.format(sym_size)) if google_icon is None: bounds = symbol.bounds(QPointF(0, 0), self.symcontext) size = bounds.width() if bounds.height() > size: size = bounds.height() size = math.ceil(size * 1.1) name = 'icon{}.png'.format(idx) path = os.path.join(self.temp_dir, name) self.png_icons.append(path) symbol.exportImage(path, "png", QSize(size, size)) kml.addfile(path) cat_style.iconstyle.scale = sym_size / 15 cat_style.iconstyle.icon.href = 'files/' + name else: cat_style.iconstyle.scale = sym_size / 10 cat_style.iconstyle.icon.href = GOOGLE_ICONS[ self.google_icons[google_icon]] cat_style.iconstyle.color = qcolor2kmlcolor( symbol.color()) elif geomtype == QgsWkbTypes.LineGeometry: # self.feedback.pushInfo(' LineGeometry') symbol_width = symbol.width() if symbol_width == 0: symbol_width = 0.5 cat_style.linestyle.color = qcolor2kmlcolor( symbol.color(), opacity) cat_style.linestyle.width = symbol_width * self.line_width_factor if name_field: cat_style.linestyle.gxlabelvisibility = True else: # self.feedback.pushInfo(' PolygonGeometry') symbol_layer = symbol.symbolLayer(0) stroke_style = symbol_layer.strokeStyle() if stroke_style == 0: stroke_width = 0 else: stroke_width = symbol_layer.strokeWidth() cat_style.linestyle.color = qcolor2kmlcolor( symbol_layer.strokeColor(), opacity) cat_style.linestyle.width = stroke_width * self.line_width_factor cat_style.polystyle.color = qcolor2kmlcolor( symbol_layer.color(), opacity) if name_field: cat_style.iconstyle.scale = 0 self.cat_styles[idx] = cat_style else: # Graduated Symbols for idx, range in enumerate(self.render.ranges()): cat_style = simplekml.Style() symbol = range.symbol() opacity = symbol.opacity() # self.feedback.pushInfo(' categories idx: {}'.format(idx)) if geomtype == QgsWkbTypes.PointGeometry: # self.feedback.pushInfo(' PointGeometry') sym_size = symbol.size(self.symcontext) color = qcolor2kmlcolor(symbol.color(), opacity) # self.feedback.pushInfo('sym_size: {}'.format(sym_size)) if google_icon is None: bounds = symbol.bounds(QPointF(0, 0), self.symcontext) size = bounds.width() if bounds.height() > size: size = bounds.height() size = math.ceil(size * 1.1) name = 'icon{}.png'.format(idx) path = os.path.join(self.temp_dir, name) self.png_icons.append(path) symbol.exportImage(path, "png", QSize(size, size)) kml.addfile(path) cat_style.iconstyle.scale = sym_size / 15 cat_style.iconstyle.icon.href = 'files/' + name else: cat_style.iconstyle.scale = sym_size / 10 cat_style.iconstyle.icon.href = GOOGLE_ICONS[ self.google_icons[google_icon]] cat_style.iconstyle.color = color elif geomtype == QgsWkbTypes.LineGeometry: # self.feedback.pushInfo(' LineGeometry') color = qcolor2kmlcolor(symbol.color(), opacity) cat_style.linestyle.color = color symbol_width = symbol.width() if symbol_width == 0: symbol_width = 0.5 cat_style.linestyle.width = symbol_width * self.line_width_factor if name_field: cat_style.linestyle.gxlabelvisibility = True else: # self.feedback.pushInfo(' PolygonGeometry') symbol_layer = symbol.symbolLayer(0) stroke_style = symbol_layer.strokeStyle() if stroke_style == 0: stroke_width = 0 else: stroke_width = symbol_layer.strokeWidth() color = qcolor2kmlcolor(symbol_layer.color(), opacity) cat_style.linestyle.color = qcolor2kmlcolor( symbol_layer.strokeColor(), opacity) cat_style.linestyle.width = stroke_width * self.line_width_factor cat_style.polystyle.color = color if name_field: cat_style.iconstyle.scale = 0 self.cat_styles[(stroke_width, color)] = cat_style def cleanup(self): for icon in self.png_icons: if os.path.exists(icon): os.remove(icon) def get_attribute_str(self, attr): if not attr: return (attr) if isinstance(attr, QDateTime): attr = attr.toString(Qt.ISODate) elif isinstance(attr, QDate): attr = attr.toString(Qt.ISODate) elif isinstance(attr, QTime): attr = attr.toString(Qt.ISODate) attr = escape('{}'.format(attr).strip()) return (attr) def exportName(self, kml_item, fname): name = self.get_attribute_str(fname) name = name.strip() kml_item.name = name def exportDescription(self, kml_item, desc, photo_path): desc = self.get_attribute_str(desc) if photo_path: desc = '<img src="{}" style="max-width:300"/><br/><br/>{}'.format( self.photos[photo_path], desc) else: desc = '{}'.format(desc).strip() if desc: kml_item.description = desc def exportFields(self, kml_item, fields, f, photo_path): strs = ['<![CDATA['] if photo_path: strs.append( '<img src="{}" style="max-width:300"/><br/><br/>'.format( self.photos[photo_path])) strs.append('<table>') for row, field in enumerate(fields): v = self.get_attribute_str(f[field]) kml_item.extendeddata.newdata(name=field, value=v, displayname=field) if row & 1: strs.append('<tr><td>{}</td><td>$[{}]</td></tr>'.format( field, field)) else: strs.append( '<tr style="background-color:#DDDDFF"><td>{}</td><td>$[{}]</td></tr>' .format(field, field)) strs.append('</table>\n]]>') str = '\n'.join(strs) kml_item.description = str def setAltitudeMode(self, kml_item, f, alt_mode, mode_field): try: mode = None if mode_field: mode = f[mode_field] if mode not in ALTITUDE_MODES and alt_mode: kml_item.altitudemode = alt_mode return if mode in ALTITUDE_MODES: kml_item.altitudemode = mode except Exception: return def parseDateTimeValues(self, feature, dt_field, date_field, time_field): if dt_field is None and date_field is None: return (None) try: dt = None date = None time = None if dt_field: dt = feature[dt_field] else: if date_field: date = feature[date_field] if time_field: time = feature[time_field] if dt: if isinstance(dt, QDateTime): year = dt.date().year() month = dt.date().month() day = dt.date().day() hour = dt.time().hour() minute = dt.time().minute() second = dt.time().second() msec = dt.time().msec() if msec == 0: str = '{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}'.format( year, month, day, hour, minute, second) else: str = '{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}.{:03d}'.format( year, month, day, hour, minute, second, msec) return (str) elif isinstance(dt, QDate): year = dt.year() month = dt.month() day = dt.day() str = '{:04d}-{:02d}-{:02d}'.format(year, month, day) return (str) elif isinstance(dt, float) or isinstance(dt, int): str = self.prepareEpochTimeString(dt) return (str) else: s = '{}'.format(dt).strip() if not s: return (None) try: # Check for EPOCH Time str = self.prepareEpochTimeString(float(s)) return (str) except ValueError: pass d1 = dateutil.parser.parse(s, default=datetime.datetime( datetime.MINYEAR, 1, 1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)) d2 = dateutil.parser.parse(s, default=datetime.datetime( datetime.MINYEAR, 2, 2, hour=1, minute=1, second=1, microsecond=1, tzinfo=None)) str = self.prepareDateString(d1, d2) return (str) else: # First format the date portion of the string if not date: return (None) # If we have a date string that only has partial values items # will be stored here. We will use it at the end if there were not # time values. date_str_partial = None if isinstance(date, QDateTime): year = date.date().year() month = date.date().month() day = date.date().day() date_str = '{:04d}-{:02d}-{:02d}'.format(year, month, day) elif isinstance(date, QDate): year = date.year() month = date.month() day = date.day() date_str = '{:04d}-{:02d}-{:02d}'.format(year, month, day) else: s = '{}'.format(date).strip() if not s: return (None) d1 = dateutil.parser.parse(s, default=datetime.datetime( datetime.MINYEAR, 1, 1)) if not time: d2 = dateutil.parser.parse(s, default=datetime.datetime( datetime.MINYEAR, 2, 2)) date_str_partial = '{:04d}'.format(d1.year) if d1.month == d2.month: date_str_partial = date_str_partial + '-{:02d}'.format( d1.month) if d1.day == d2.day: date_str_partial = date_str_partial + '-{:02d}'.format( d1.day) date_str = '{:04d}-{:02d}-{:02d}'.format( d1.year, d1.month, d1.day) # Format the time portion string time_str = None if time: if isinstance(time, QDateTime): hour = time.time().hour() minute = time.time().minute() second = time.time().second() msec = time.time().msec() if msec == 0: time_str = '{:02d}:{:02d}:{:02d}'.format( hour, minute, second) else: time_str = '{:02d}:{:02d}:{:02d}.{:03d}'.format( hour, minute, second, msec) elif isinstance(time, QTime): hour = time.hour() minute = time.minute() second = time.second() msec = time.msec() if msec == 0: time_str = '{:02d}:{:02d}:{:02d}'.format( hour, minute, second) else: time_str = '{:02d}:{:02d}:{:02d}.{:03d}'.format( hour, minute, second, msec) else: s = '{}'.format(time).strip() if not s: return (None) d = dateutil.parser.parse(s, default=datetime.datetime( datetime.MINYEAR, 1, 1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)) time_str = '{:02d}:{:02d}:{:02d}'.format( d.hour, d.minute, d.second) if time_str: return (date_str + 'T' + time_str) else: if date_str_partial: return (date_str_partial) return (date_str) except Exception: '''s = traceback.format_exc() self.feedback.pushInfo(s)''' return (None) def prepareEpochTimeString(self, dt): edt = datetime.datetime.fromtimestamp(dt) year = edt.year month = edt.month day = edt.day hour = edt.hour minute = edt.minute second = edt.second microsec = edt.microsecond if microsec == 0: str = '{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}'.format( year, month, day, hour, minute, second) else: str = '{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}.{:03d}'.format( year, month, day, hour, minute, second, int(microsec / 1000)) return (str) def prepareDateString(self, d1, d2): # if only parts of the date are valid then just return those portions # otherwise return a fully formatted iso string. # self.feedback.pushInfo('{} {} {} {} {} {}'.format(d1.year, d1.month, d1.day, d2.year, d2.month, d2.day)) if d1.year == datetime.MINYEAR: return (None) str = '{:04d}'.format(d1.year) if d1.month != d2.month: return (str) str = str + '-{:02d}'.format(d1.month) if d1.day != d2.day: return (str) str = str + '-{:02d}'.format(d1.day) if d1.hour != d2.hour: return (str) # We have a valid date and time string so just return the iso formated string str = d1.isoformat() return (str) def name(self): return 'exportkmz' def icon(self): return QIcon(os.path.dirname(__file__) + '/icons/export.svg') def displayName(self): return 'Export KMZ' def group(self): return 'Vector conversion' def groupId(self): return 'vectorconversion' def helpUrl(self): file = os.path.dirname(__file__) + '/index.html' if not os.path.exists(file): return '' return QUrl.fromLocalFile(file).toString(QUrl.FullyEncoded) def createInstance(self): return ExportKmzAlgorithm()
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 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 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 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, 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, 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 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 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 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 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 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 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 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 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 fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() # add a new scope corresponding to the source layer -- this will potentially overwrite any other # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure # that source layer fields and attributes are present in the context context.appendScope( self.source_layer.createExpressionContextScope()) self.settings.data_defined_properties.prepare(context) self.fetch_layout_properties(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.visible_features_only and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.visible_features_only and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) # Some plot types don't draw individual glyphs for each feature, but aggregate them instead. # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each # feature. Instead, the evaluation should be executed only once for these settings. aggregating = self.settings.plot_type in ['box', 'histogram'] executed = False xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['in_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead colors.append(default_value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['out_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color stroke_colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_STROKE_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] stroke_colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead stroke_colors.append(default_value.name()) executed = True self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
def 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)