def set_feature_ids(self, layer: Optional[QgsVectorLayer], fids: List[int]): """ Sets the feature ids to show in the model """ self.beginRemoveRows(QModelIndex(), 0, len(self.features)) self.features = [] self.display_expressions = [] self.endRemoveRows() if layer is None: self.fids = [] return context = QgsExpressionContext() context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer)) display_expression = QgsExpression(layer.displayExpression()) display_expression.prepare(context) request = QgsFeatureRequest().setFilterFids(fids) request.setSubsetOfAttributes(display_expression.referencedColumns(), layer.fields()) pending_features = list(layer.getFeatures(request)) self.beginInsertRows(QModelIndex(), 0, len(pending_features)) self.features = pending_features self.fids = fids for f in self.features: context.setFeature(f) self.display_expressions.append(display_expression.evaluate(context)) self.endInsertRows()
def evaluate_expressions( exp: QgsExpression, feature: Optional[QgsFeature] = None, layer: Optional[QgsMapLayer] = None, context_scopes: Optional[List[QgsExpressionContextScope]] = None, ) -> Union[bool, int, str, float, None]: """ Evaluate a QGIS expression :param exp: QGIS expression :param feature: Optional QgsFeature :param layer: Optional QgsMapLayer :param context_scopes: Optional list of QgsExpressionContextScopes :return: evaluated value of the expression """ context = QgsExpressionContext() scopes = context_scopes if context_scopes is not None else [] if layer: scopes.append(QgsExpressionContextUtils.layerScope(layer)) context.appendScopes(scopes) if feature: context.setFeature(feature) value = exp.evaluate(context) if exp.hasParserError(): raise QgsPluginExpressionException( bar_msg=bar_msg(exp.parserErrorString())) if exp.hasEvalError(): raise QgsPluginExpressionException( bar_msg=bar_msg(exp.evalErrorString())) return value
def getLayerCategoryNode(self, lyr, rootNode, categoryExpression): """ Finds category node based on category expression and creates it (if not exists a node) """ exp = QgsExpression(categoryExpression) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(lyr) ) if exp.hasParserError(): raise Exception(exp.parserErrorString()) if exp.hasEvalError(): raise ValueError(exp.evalErrorString()) categoryText = exp.evaluate(context) return self.createGroup(categoryText, rootNode)
def calc_height_difference(layer, column_prefix, band_number, secur_dist): """ Calculate the height difference between the raster height value and the coordinates Add the result to the 'Delta' field """ try: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) # Qgis V3.14 -> V3.16 remove the "_" between the name and the band number field_names = [field.name() for field in layer.fields() ] ## List of the layer fields if column_prefix + "_" + band_number in field_names: prep_expr_1 = "round({}, 3)".format(column_prefix + "_" + band_number) else: prep_expr_1 = "round({}, 3)".format(column_prefix + band_number) expression1 = QgsExpression(prep_expr_1) layer.startEditing() for feat in layer.getFeatures(): context.setFeature(feat) feat["H_avoir"] = expression1.evaluate(context) layer.updateFeature(feat) layer.commitChanges() prep_expr_2 = "round((H_doit - H_avoir - {}), 3)".format( secur_dist) expression2 = QgsExpression(prep_expr_2) layer.startEditing() for feat in layer.getFeatures(): context.setFeature(feat) feat["Delta"] = expression2.evaluate(context) layer.updateFeature(feat) layer.commitChanges() except: print( "Visibility calculation -> Failed to calculate the height difference between the raster height value and the coordinates" )
def style_balance(layer, qml: str, positive_col1: str, positive_col2: str, positive_col3: str, negative_col1: str, negative_col2: str, negative_col3: str): layer.loadNamedStyle(qml) positive_columns = [] negative_columns = [] for col in [positive_col1, positive_col2, positive_col3]: if col != '': positive_columns.append('coalesce({col}, 0)'.format(col=col)) for col in [negative_col1, negative_col2, negative_col3]: if col != '': negative_columns.append('coalesce({col}, 0)'.format(col=col)) class_attribute_string = '({pos})-({neg})'.format( pos=' + '.join(positive_columns), neg=' + '.join(negative_columns)) layer.renderer().setClassAttribute(class_attribute_string) layer.renderer().deleteAllClasses() min_expression = QgsExpression( 'minimum({})'.format(class_attribute_string)) max_expression = QgsExpression( 'maximum({})'.format(class_attribute_string)) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) min_val = min_expression.evaluate(context) max_val = max_expression.evaluate(context) abs_max = max(abs(min_val), abs(max_val)) class_bounds = list( np.arange(abs_max * -1, abs_max, ((abs_max - abs_max * -1) / 10.0))) class_bounds.append(abs_max) for i in range(len(class_bounds) - 1): layer.renderer().addClassLowerUpper(lower=class_bounds[i], upper=class_bounds[i + 1]) color_ramp = layer.renderer().sourceColorRamp() layer.renderer().updateColorRamp(color_ramp) layer.triggerRepaint() utils.iface.layerTreeView().refreshLayerSymbology(layer.id())
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 fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() self.settings.data_defined_properties.prepare(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.visible_features_only and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.visible_features_only and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR): default_value = QColor(self.settings.properties['in_color']) value, _ = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) colors.append(value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR): default_value = QColor(self.settings.properties['out_color']) value, _ = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) stroke_colors.append(value.name()) self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
def 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'}
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 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 addDroppedDocument(self, fileUrl): if self.checkLayerEditingMode() is False: return # Workaround because of QGIS not resetting this property after linking features self.editorContext().vectorLayerTools().setForceSuppressFormPopup( False) layer = self.relation().referencingLayer() if self.nmRelation().isValid(): layer = self.nmRelation().referencedLayer() default_documents_path = str() if self.documents_path: exp = QgsExpression(self.documents_path) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) default_documents_path = str(exp.evaluate(context)) filename = QUrl(fileUrl).toLocalFile() if default_documents_path: filename = QDir(default_documents_path).relativeFilePath(filename) keyAttrs = dict() # Fields of the linking table fields = self.relation().referencingLayer().fields() # For generated relations insert the referenced layer field if self.relation().type() == QgsRelation.Generated: polyRel = self.relation().polymorphicRelation() keyAttrs[fields.indexFromName( polyRel.referencedLayerField())] = polyRel.layerRepresentation( self.relation().referencedLayer()) if self.nmRelation().isValid(): # only normal relations support m:n relation if self.nmRelation().type() != QgsRelation.Normal: QMessageBox.critical( self, self.tr("Add document"), self. tr("Invalid relation, Only normal relations support m:n relation." )) return # Pre fill inserting document filepath attributes = { self.nmRelation().referencedLayer().fields().indexFromName(self.document_filename): filename } # n:m Relation: first let the user create a new feature on the other table # and autocreate a new linking feature. ok, feature = self.editorContext().vectorLayerTools().addFeature( self.nmRelation().referencedLayer(), attributes, QgsGeometry()) if not ok: QMessageBox.critical( self, self.tr("Add document"), self.tr("Could not add a new linking feature.")) return for key in self.relation().fieldPairs(): keyAttrs[fields.indexOf(key)] = self.feature().attribute( self.relation().fieldPairs()[key]) for key in self.nmRelation().fieldPairs(): keyAttrs[fields.indexOf(key)] = feature.attribute( self.nmRelation().fieldPairs()[key]) linkFeature = QgsVectorLayerUtils.createFeature( self.relation().referencingLayer(), QgsGeometry(), keyAttrs, self.relation().referencingLayer().createExpressionContext()) if not self.relation().referencingLayer().addFeature(linkFeature): QMessageBox.critical(self, self.tr("Add document"), self.tr("Could not add a new feature.")) return else: for key in self.relation().fieldPairs(): keyAttrs[fields.indexFromName(key)] = self.feature().attribute( self.relation().fieldPairs()[key]) # Pre fill inserting document filepath keyAttrs[fields] = filename ok, feature = self.editorContext().vectorLayerTools().addFeature( self.relation().referencingLayer(), keyAttrs, QgsGeometry()) if not ok: QMessageBox.critical(self, self.tr("Add document"), self.tr("Could not add a new feature.")) return self.updateUi()
def reloadData(self): self.beginResetModel() self._document_list = [] if self._relation.isValid() is False or self._feature.isValid( ) is False: self.endResetModel() return feature_list = [] layer = self._relation.referencingLayer() request = self._relation.getRelatedFeaturesRequest(self._feature) for feature in layer.getFeatures(request): feature_list.append(feature) if self._nmRelation.isValid(): filters = [] for joinTableFeature in feature_list: referencedFeatureRequest = self._nmRelation.getReferencedFeatureRequest( joinTableFeature) filterExpression = referencedFeatureRequest.filterExpression() filters.append("(" + filterExpression.expression() + ")") nmRequest = QgsFeatureRequest() nmRequest.setFilterExpression(" OR ".join(filters)) feature_list = [] layer = self._nmRelation.referencedLayer() for documentFeature in layer.getFeatures(nmRequest): feature_list.append(documentFeature) for documentFeature in feature_list: documents_path = str() if self._documents_path: exp = QgsExpression(self._documents_path) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) context.setFeature(documentFeature) documents_path = exp.evaluate(context) document_filename = str() if self._document_filename: exp = QgsExpression(self._document_filename) context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(layer)) context.setFeature(documentFeature) document_filename = exp.evaluate(context) file_info = QFileInfo(QDir(str(documents_path)), str(document_filename)) # ToolTip toolTipList = [] toolTipList.append("<ul>") for field in documentFeature.fields(): index = documentFeature.fields().indexFromName(field.name()) toolTipList.append("<li><strong>{0}</strong>: {1}</li>".format( field.displayName(), documentFeature[index])) toolTipList.append("</ul>") self._document_list.append({ self.DocumentIdRole: documentFeature.id(), self.DocumentPathRole: file_info.filePath(), self.DocumentNameRole: file_info.fileName(), self.DocumentExistsRole: file_info.exists(), self.DocumentToolTipRole: "".join(toolTipList), self.DocumentIsImageRole: PreviewImageProvider.isMimeTypeSupported(file_info.filePath()) }) self.endResetModel()
def create_mesure_layer(self): """ Create an temporary point layer in the Qgis canvas """ try: QApplication.setOverrideCursor( Qt.WaitCursor) ## Start the 'wait' cursor if self.list_mesures: vl = QgsVectorLayer("Linestring?crs=" + self.selected_epsg, self.layer_name, "memory") pr = vl.dataProvider() # add fields pr.addAttributes([ QgsField("station", QVariant.String), QgsField("st_num", QVariant.Int), QgsField("st_y", QVariant.Double, "double", 12, 4), QgsField("st_x", QVariant.Double, "double", 12, 4), QgsField("st_h", QVariant.Double, "double", 10, 4), QgsField("st_hi", QVariant.Double), QgsField("vise", QVariant.String), QgsField("vise_y", QVariant.Double, "double", 12, 4), QgsField("vise_x", QVariant.Double, "double", 12, 4), QgsField("vise_h", QVariant.Double, "double", 10, 4), QgsField("vise_hs", QVariant.Double), QgsField("vise_category", QVariant.Int), QgsField("dhz", QVariant.Double), QgsField("dh", QVariant.Double) ]) vl.updateFields( ) # tell the vector layer to fetch changes from the provider # add features for item in self.list_mesures: st_y = float(item[2]) st_x = float(item[3]) vise_y = float(item[7]) vise_x = float(item[8]) fet = QgsFeature() fet.setGeometry( QgsGeometry.fromPolyline( [QgsPoint(st_y, st_x), QgsPoint(vise_y, vise_x)])) fet.setAttributes(list(item)) pr.addFeatures([fet]) # Calculate the "dhz" and "dh" columns expression1 = QgsExpression( "round((sqrt((st_y - vise_y)^2 + (st_x - vise_x)^2)), 3)") expression2 = QgsExpression( "round((vise_h + vise_hs - st_h - st_hi), 3)") context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes(vl)) vl.startEditing() for f in vl.getFeatures(): context.setFeature(f) f["dhz"] = expression1.evaluate(context) f["dh"] = expression2.evaluate(context) vl.updateFeature(f) vl.commitChanges() # add preconfigured qgis.qml style file plugin_folder = os.path.dirname(os.path.dirname(__file__)) qml_file = Path(plugin_folder) / "qml" / self.qml_style_mesure if qml_file.is_file( ): # Test if file exist, avoid error if he is missing vl.loadNamedStyle(str(qml_file)) # update layer's extent when new features have been added vl.updateExtents() # zoom to the layer extent canvas = iface.mapCanvas() canvas.setExtent(vl.extent()) # Show in project self.rmv_old_qgs_mesure_layer() QgsProject.instance().addMapLayer(vl) except: print( "Mesures -> Failed to create a new measurements Qgis layer (def create_mesure_layer)" ) QApplication.restoreOverrideCursor() ## Stop the 'wait' cursor finally: QApplication.restoreOverrideCursor() ## Stop the 'wait' cursor