Example #1
0
    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()
Example #2
0
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
Example #3
0
 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)
Example #4
0
    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"
            )
Example #5
0
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())
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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()
Example #10
0
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')
Example #11
0
    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()
Example #12
0
    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()
Example #13
0
    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