示例#1
0
    def createField(self, ft_attribute):
        typename = None
        if ft_attribute.type == "LONG":
            variant = QVariant.LongLong
        elif ft_attribute.type == "DOUBLE":
            variant = QVariant.Double
        elif ft_attribute.type == "FLOAT":
            variant = QVariant.Double  # QVariant.Float
            typename = "float4"
        elif ft_attribute.type == "INTEGER":
            variant = QVariant.Int
        elif ft_attribute.type == "BOOLEAN":
            variant = QVariant.Bool
        elif ft_attribute.type == "STRING":
            variant = QVariant.String
        elif ft_attribute.type == "BYTE":
            variant = 37  # QVariant.UChar= Byte
        elif ft_attribute.type == "SHORT":
            variant = QVariant.Int  # QVariant.Short
            typename = "int2"
        elif ft_attribute.type == "CHAR":
            variant = 7  # QVariant.QChar
        elif ft_attribute.type == "UUID":
            variant = QVariant.Uuid
        elif ft_attribute.type == "DATETIME":
            variant = QVariant.DateTime
        elif ft_attribute.type == "DATE":
            variant = QVariant.Date
        elif ft_attribute.type == "TIME":
            variant = QVariant.Time
        elif ft_attribute.type == "TIMESTAMP":
            variant = QVariant.DateTime  # NOTE: this will cause issues when writing, but ...
        else:
            raise Exception("unknown type -" + ft_attribute.type)

        name = ft_attribute.name
        if name.startswith("new.") or name.startswith("old."):
            name = name[4:]
        elif name.startswith("ancestor."):
            name = name[9:]
        elif name.startswith("theirs."):
            name = name[7:]
        elif name.startswith("ours."):
            name = name[5:]
        field = QgsField(name, variant)
        if typename is not None:
            field.setTypeName(typename)
        return field
示例#2
0
def add_attribute(proposed_attr_name, dtype, layer):
    if dtype == 'S':
        qtype = QVariant.String
        qname = 'String'
    elif dtype in ('U', 'I'):  # FIXME: what for unsigned int?
        qtype = QVariant.Int
        qname = 'integer'
    else:  # FIXME: treating everything else as double (it might be wrong)
        qtype = QVariant.Double
        qname = 'double'
    field = QgsField(proposed_attr_name, qtype)
    field.setTypeName(qname)
    assigned_attr_names = ProcessLayer(layer).add_attributes(
        [field])
    assigned_attr_name = assigned_attr_names[proposed_attr_name]
    return assigned_attr_name
示例#3
0
 def test_find_attribute_id(self):
     field_names = ['first', 'second']
     field_one = QgsField(field_names[0], QVariant.String)
     field_one.setTypeName(STRING_FIELD_TYPE_NAME)
     field_two = QgsField(field_names[1], QVariant.Int)
     field_two.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_one, field_two]
     ProcessLayer(self.layer).add_attributes(attributes)
     added_field_names = [field.name() for field in self.layer.fields()]
     # Check that both attributes are correctly found
     for attr_name in added_field_names:
         # it raises AttributeError if not found
         ProcessLayer(self.layer).find_attribute_id(attr_name)
     # Check that an inexistent field doesn't get found and that the
     # AttributeError exception is correctly raised
     with self.assertRaises(AttributeError):
         ProcessLayer(self.layer).find_attribute_id('dummy')
 def test_find_attribute_id(self):
     field_names = ['first', 'second']
     field_one = QgsField(field_names[0], QVariant.String)
     field_one.setTypeName(STRING_FIELD_TYPE_NAME)
     field_two = QgsField(field_names[1], QVariant.Int)
     field_two.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_one, field_two]
     ProcessLayer(self.layer).add_attributes(attributes)
     added_field_names = [field.name() for field in self.dp.fields()]
     # Double-check that add_attributes is working properly
     assert added_field_names == field_names
     # Check that both attributes are correctly found
     for attr_name in field_names:
         try:
             ProcessLayer(self.layer).find_attribute_id(attr_name)
         except AttributeError:
             print "We would expect both attributes to be found!"
             raise
     # Check that an inexistent field doesn't get found and that the
     # AttributeError exception is correctly raised
     with self.assertRaises(AttributeError):
         ProcessLayer(self.layer).add_attributes('dummy')
示例#5
0
def add_textual_attribute(proposed_attr_name, layer):
    field = QgsField(proposed_attr_name, QVariant.String)
    field.setTypeName(STRING_FIELD_TYPE_NAME)
    assigned_attr_names = ProcessLayer(layer).add_attributes([field])
    assigned_attr_name = assigned_attr_names[proposed_attr_name]
    return assigned_attr_name
示例#6
0
def add_numeric_attribute(proposed_attr_name, layer):
    field = QgsField(proposed_attr_name, QVariant.Double)
    field.setTypeName(DOUBLE_FIELD_TYPE_NAME)
    assigned_attr_names = ProcessLayer(layer).add_attributes([field])
    assigned_attr_name = assigned_attr_names[proposed_attr_name]
    return assigned_attr_name
示例#7
0
    def transform_attribute(
            self, input_attr_name, algorithm_name, variant="",
            inverse=False, new_attr_name=None, simulate=False):
        """
        Use one of the available transformation algorithms to transform an
        attribute of the layer, and add a new attribute with the
        transformed data
        """
        # get the id of the attribute named input_attr_name
        input_attr_id = self.find_attribute_id(input_attr_name)

        # build the name of the output transformed attribute
        # WARNING! Shape files support max 10 chars for attribute names
        if not new_attr_name:
            if variant:
                new_attr_name = algorithm_name[:5] + '_' + variant[:4]
            else:
                new_attr_name = algorithm_name[:10]
        else:
            new_attr_name = new_attr_name[:10]
        new_attr_name = new_attr_name.replace(' ', '_')
        field = QgsField(new_attr_name, QVariant.Double)
        field.setTypeName(DOUBLE_FIELD_TYPE_NAME)
        if simulate:
            attr_names_dict = self.add_attributes([field], simulate=simulate)
            # get the name actually assigned to the new attribute
            actual_new_attr_name = attr_names_dict[new_attr_name]
            return actual_new_attr_name

        # a dict will contain all the values for the chosen input attribute,
        # keeping as key, for each value, the id of the corresponding feature
        initial_dict = dict()
        for feat in self.layer.getFeatures():
            initial_dict[feat.id()] = feat[input_attr_id]

        # get the transformation algorithm from the register
        algorithm = TRANSFORMATION_ALGS[algorithm_name]

        # transform the values in the dictionary with the chosen algorithm
        try:
            transformed_dict = transform(
                initial_dict, algorithm, variant, inverse)
        except ValueError:
            raise
        except NotImplementedError:
            raise

        attr_names_dict = self.add_attributes([field], simulate=simulate)
        # get the name actually assigned to the new attribute
        actual_new_attr_name = attr_names_dict[new_attr_name]
        # get the id of the new attribute
        new_attr_id = self.find_attribute_id(actual_new_attr_name)

        with LayerEditingManager(
                self.layer, 'Write transformed values', DEBUG):
            for feat in self.layer.getFeatures():
                feat_id = feat.id()
                value = transformed_dict[feat_id]
                if type(value) not in (QPyNullVariant, NoneType):
                    value = float(value)
                self.layer.changeAttributeValue(feat_id, new_attr_id, value)
        return actual_new_attr_name
示例#8
0
def calculate_zonal_stats(loss_layer,
                          zonal_layer,
                          loss_attr_names,
                          loss_layer_is_vector,
                          zone_id_in_losses_attr_name,
                          zone_id_in_zones_attr_name,
                          iface,
                          extra=True):
    """
    :param loss_layer: vector or raster layer containing loss data points
    :param zonal_layer: vector layer containing zonal data
    :param loss_attr_names: names of the loss layer fields to be aggregated
    :param loss_layer_is_vector: True if the loss layer is a vector layer
    :param zone_id_in_losses_attr_name:
        name of the field containing the zone id where each loss point belongs
        (or None)
    :param zone_id_in_zones_attr_name:
        name of the field containing the id of each zone (or None)
    :param iface: QGIS interface
    :param extra:
        if True, also NUM_POINTS and AVG will be added

    At the end of the workflow, we will have, for each feature (zone):

    * a "NUM_POINTS" attribute, specifying how many points are
      inside the zone (added if extra=True)
    * for each variable:
        * a "SUM" attribute, summing the values for all the
          points that are inside the zone
        * a "AVG" attribute, averaging for each zone (added if extra=True)
    """

    # add count, sum and avg fields for aggregating statistics
    # (one new attribute for the count of points, then a sum and an average
    # for all the other loss attributes)
    # TODO remove debugging trace
    loss_attrs_dict = {}
    if extra:  # adding also NUM_POINTS and AVG
        count_field = QgsField('NUM_POINTS', QVariant.Int)
        count_field.setTypeName(INT_FIELD_TYPE_NAME)
        count_added = \
            ProcessLayer(zonal_layer).add_attributes([count_field])
        # add_attributes returns a dict
        #     proposed_attr_name -> assigned_attr_name
        # so the actual count attribute name is the first value of the dict
        loss_attrs_dict['count'] = count_added.values()[0]
    for loss_attr_name in loss_attr_names:
        loss_attrs_dict[loss_attr_name] = {}
        sum_field = QgsField('SUM_%s' % loss_attr_name, QVariant.Double)
        sum_field.setTypeName(DOUBLE_FIELD_TYPE_NAME)
        sum_added = \
            ProcessLayer(zonal_layer).add_attributes([sum_field])
        # see comment above
        loss_attrs_dict[loss_attr_name]['sum'] = sum_added.values()[0]
        if extra:  # adding also NUM_POINTS and AVG
            avg_field = QgsField('AVG_%s' % loss_attr_name, QVariant.Double)
            avg_field.setTypeName(DOUBLE_FIELD_TYPE_NAME)
            avg_added = \
                ProcessLayer(zonal_layer).add_attributes([avg_field])
            # see comment above
            loss_attrs_dict[loss_attr_name]['avg'] = avg_added.values()[0]
    if loss_layer_is_vector:
        # check if the user specified that the loss_layer contains an
        # attribute specifying what's the zone id for each loss point
        if zone_id_in_losses_attr_name:
            # then we can aggregate by zone id, instead of doing a
            # geo-spatial analysis to see in which zone each point is
            res = calculate_vector_stats_aggregating_by_zone_id(
                loss_layer,
                zonal_layer,
                zone_id_in_losses_attr_name,
                zone_id_in_zones_attr_name,
                loss_attr_names,
                loss_attrs_dict,
                iface,
                extra=extra)
            (loss_layer, zonal_layer, loss_attrs_dict) = res
        else:
            if not zone_id_in_zones_attr_name:
                # we need to acquire the zones' geometries from the
                # zonal layer and check if loss points are inside those zones
                # In order to be sure to avoid duplicate zone names, we add to
                # the zonal layer an additional field and copy into that the
                # unique id of each feature
                proposed_attr_name = 'ZONE_ID'
                new_attr = QgsField(proposed_attr_name, QVariant.Int)
                new_attr.setTypeName(INT_FIELD_TYPE_NAME)
                attr_dict = \
                    ProcessLayer(zonal_layer).add_attributes([new_attr])
                # we get a dict, from which we find the actual attribute name
                # in the only dict value
                zone_id_in_zones_attr_name = attr_dict.values()[0]
                with edit(zonal_layer):
                    unique_id_idx = zonal_layer.fieldNameIndex(
                        zone_id_in_zones_attr_name)
                    for feat in zonal_layer.getFeatures():
                        zonal_layer.changeAttributeValue(
                            feat.id(), unique_id_idx, feat.id())

            (_, loss_layer_plus_zones,
             zone_id_in_losses_attr_name) = add_zone_id_to_points(
                 iface, loss_layer, zonal_layer, zone_id_in_zones_attr_name)

            old_field_to_new_field = {}
            for idx, field in enumerate(loss_layer.fields()):
                old_field_to_new_field[field.name()] = \
                    loss_layer_plus_zones.fields()[idx].name()

            res = calculate_vector_stats_aggregating_by_zone_id(
                loss_layer_plus_zones,
                zonal_layer,
                zone_id_in_losses_attr_name,
                zone_id_in_zones_attr_name,
                loss_attr_names,
                loss_attrs_dict,
                iface,
                old_field_to_new_field,
                extra=extra)
            (loss_layer, zonal_layer, loss_attrs_dict) = res

    else:
        (loss_layer, zonal_layer) = \
            calculate_raster_stats(loss_layer, zonal_layer)
    return loss_layer, zonal_layer, loss_attrs_dict
示例#9
0
def _add_zone_id_to_points_internal(iface, loss_layer, zonal_layer,
                                    zone_id_in_zones_attr_name):
    """
       On the hypothesis that we don't know what is the zone in which
       each point was collected we use an alternative implementation of what
       SAGA does, i.e.,
       we add a field to the loss layer, containing the id of the zone
       to which it belongs. In order to achieve that:
       * we create a spatial index of the loss points
       * for each zone (in the layer containing zonally-aggregated SVI
           * we identify points that are inside the zone's bounding box
           * we check if each of these points is actually inside the
               zone's geometry; if it is:
               * copy the zone id into the new field of the loss point
       Notes:
       * loss_layer contains the not aggregated loss points
       * zonal_layer contains the zone geometries
       """
    # make a copy of the loss layer and use that from now on
    add_to_registry = True if DEBUG else False
    loss_layer_plus_zones = \
        ProcessLayer(loss_layer).duplicate_in_memory(
                new_name='Loss plus zone labels',
                add_to_registry=add_to_registry)
    # add to it the new attribute that will contain the zone id
    # and to do that we need to know the type of the zone id field
    zonal_layer_fields = zonal_layer.fields()
    zone_id_field_variant, zone_id_field_type_name = [
        (field.type(), field.typeName()) for field in zonal_layer_fields
        if field.name() == zone_id_in_zones_attr_name
    ][0]
    zone_id_field = QgsField(zone_id_in_zones_attr_name, zone_id_field_variant)
    zone_id_field.setTypeName(zone_id_field_type_name)
    assigned_attr_names_dict = \
        ProcessLayer(loss_layer_plus_zones).add_attributes(
                [zone_id_field])
    zone_id_in_losses_attr_name = assigned_attr_names_dict.values()[0]
    # get the index of the new attribute, to be used to update its values
    zone_id_attr_idx = loss_layer_plus_zones.fieldNameIndex(
        zone_id_in_losses_attr_name)
    # to show the overall progress, cycling through points
    tot_points = loss_layer_plus_zones.featureCount()
    msg = tr("Step 2 of 3: creating spatial index for loss points...")
    msg_bar_item, progress = create_progress_message_bar(
        iface.messageBar(), msg)
    # create spatial index
    with TraceTimeManager(tr("Creating spatial index for loss points..."),
                          DEBUG):
        spatial_index = QgsSpatialIndex()
        for current_point, loss_feature in enumerate(
                loss_layer_plus_zones.getFeatures()):
            progress_perc = current_point / float(tot_points) * 100
            progress.setValue(progress_perc)
            spatial_index.insertFeature(loss_feature)
    clear_progress_message_bar(iface.messageBar(), msg_bar_item)
    with edit(loss_layer_plus_zones):
        # to show the overall progress, cycling through zones
        tot_zones = zonal_layer.featureCount()
        msg = tr("Step 3 of 3: labeling points by zone id...")
        msg_bar_item, progress = create_progress_message_bar(
            iface.messageBar(), msg)
        for current_zone, zone_feature in enumerate(zonal_layer.getFeatures()):
            progress_perc = current_zone / float(tot_zones) * 100
            progress.setValue(progress_perc)
            msg = "{0}% - Zone: {1} on {2}".format(progress_perc,
                                                   zone_feature.id(),
                                                   tot_zones)
            with TraceTimeManager(msg, DEBUG):
                zone_geometry = zone_feature.geometry()
                # Find ids of points within the bounding box of the zone
                point_ids = spatial_index.intersects(
                    zone_geometry.boundingBox())
                # check if the points inside the bounding box of the zone
                # are actually inside the zone's geometry
                for point_id in point_ids:
                    msg = "Checking if point {0} is actually inside " \
                          "the zone".format(point_id)
                    with TraceTimeManager(msg, DEBUG):
                        # Get the point feature by the point's id
                        request = QgsFeatureRequest().setFilterFid(point_id)
                        point_feature = loss_layer_plus_zones.getFeatures(
                            request).next()
                        point_geometry = QgsGeometry(point_feature.geometry())
                        # check if the point is actually inside the zone
                        # and it is not only contained by its bounding box
                        if zone_geometry.contains(point_geometry):
                            zone_id = zone_feature[zone_id_in_zones_attr_name]
                            loss_layer_plus_zones.changeAttributeValue(
                                point_id, zone_id_attr_idx, zone_id)
        # for consistency with the SAGA algorithm, remove points that don't
        # belong to any zone
        for point_feature in loss_layer_plus_zones.getFeatures():
            if not point_feature[zone_id_in_losses_attr_name]:
                loss_layer_plus_zones.deleteFeature(point_feature.id())
    clear_progress_message_bar(iface.messageBar(), msg_bar_item)
    return loss_layer_plus_zones, zone_id_in_losses_attr_name
示例#10
0
def calculate_svi(
    iface, current_layer, project_definition, indicators_operator=None, themes_operator=None, reuse_field=False
):
    """
    add an SVI attribute to the current layer
    """
    # set default
    if indicators_operator is None:
        indicators_operator = DEFAULT_COMBINATION
    if themes_operator is None:
        themes_operator = DEFAULT_COMBINATION

    themes = project_definition["children"][1]["children"]

    if reuse_field and "svi_field" in project_definition:
        svi_attr_name = project_definition["svi_field"]
        if DEBUG:
            print "Reusing %s" % svi_attr_name
    else:
        svi_attr_name = "SVI"
        svi_field = QgsField(svi_attr_name, QVariant.Double)
        svi_field.setTypeName(DOUBLE_FIELD_TYPE_NAME)
        attr_names = ProcessLayer(current_layer).add_attributes([svi_field])
        svi_attr_name = attr_names[svi_attr_name]

    # get the id of the new attribute
    svi_attr_id = ProcessLayer(current_layer).find_attribute_id(svi_attr_name)

    discarded_feats_ids = []
    try:
        with LayerEditingManager(current_layer, "Add SVI", DEBUG):
            for feat in current_layer.getFeatures():
                # If a feature contains any NULL value, discard_feat will
                # be set to True and the corresponding SVI will be set to
                # NULL
                discard_feat = False
                feat_id = feat.id()

                # init svi_value to the correct value depending on
                # themes_operator
                if themes_operator in SUM_BASED_COMBINATIONS:
                    svi_value = 0
                elif themes_operator in MUL_BASED_COMBINATIONS:
                    svi_value = 1

                # iterate all themes of SVI
                for theme in themes:
                    indicators = theme["children"]

                    # init theme_result to the correct value depending on
                    # indicators_operator
                    if indicators_operator in SUM_BASED_COMBINATIONS:
                        theme_result = 0
                    elif indicators_operator in MUL_BASED_COMBINATIONS:
                        theme_result = 1

                    # iterate all indicators of a theme
                    for indicator in indicators:
                        if feat[indicator["field"]] == QPyNullVariant(float):
                            discard_feat = True
                            discarded_feats_ids.append(feat_id)
                            break
                        # For "Average (equal weights)" it's equivalent to use
                        # equal weights, or to sum the indicators
                        # (all weights 1)
                        # and divide by the number of indicators (we use
                        # the latter solution)
                        if indicators_operator in (
                            "Sum (simple)",
                            "Average (equal weights)",
                            "Multiplication (simple)",
                        ):
                            indicator_weighted = feat[indicator["field"]]
                        else:
                            indicator_weighted = feat[indicator["field"]] * indicator["weight"]

                        if indicators_operator in SUM_BASED_COMBINATIONS:
                            theme_result += indicator_weighted
                        elif indicators_operator in MUL_BASED_COMBINATIONS:
                            theme_result *= indicator_weighted
                        else:
                            error_message = "invalid indicators_operator: %s" % indicators_operator
                            raise RuntimeError(error_message)
                    if discard_feat:
                        break
                    if indicators_operator == "Average (equal weights)":
                        theme_result /= len(indicators)

                    # combine the indicators of each theme
                    # For "Average (equal weights)" it's equivalent to use
                    # equal weights, or to sum the themes (all weights 1)
                    # and divide by the number of themes (we use
                    # the latter solution)
                    if themes_operator in ("Sum (simple)", "Average (equal weights)", "Multiplication (simple)"):
                        theme_weighted = theme_result
                    else:
                        theme_weighted = theme_result * theme["weight"]

                    if themes_operator in SUM_BASED_COMBINATIONS:
                        svi_value += theme_weighted
                    elif themes_operator in MUL_BASED_COMBINATIONS:
                        svi_value *= theme_weighted
                if discard_feat:
                    svi_value = QPyNullVariant(float)
                else:
                    if themes_operator == "Average (equal weights)":
                        svi_value /= len(themes)

                current_layer.changeAttributeValue(feat_id, svi_attr_id, svi_value)
        msg = (
            "The SVI has been calculated for fields containing "
            "non-NULL values and it was added to the layer as "
            "a new attribute called %s"
        ) % svi_attr_name
        iface.messageBar().pushMessage(tr("Info"), tr(msg), level=QgsMessageBar.INFO)
        if discarded_feats_ids:
            widget = toggle_select_features_widget(
                tr("Warning"),
                tr("Invalid indicators were found in some features while " "calculating SVI"),
                tr("Select invalid features"),
                current_layer,
                discarded_feats_ids,
                current_layer.selectedFeaturesIds(),
            )
            iface.messageBar().pushWidget(widget, QgsMessageBar.WARNING)

        project_definition["indicators_operator"] = indicators_operator
        project_definition["themes_operator"] = themes_operator
        project_definition["svi_field"] = svi_attr_name
        return svi_attr_id, discarded_feats_ids

    except TypeError as e:
        current_layer.dataProvider().deleteAttributes([svi_attr_id])
        msg = "Could not calculate SVI due to data problems: %s" % e
        iface.messageBar().pushMessage(tr("Error"), tr(msg), level=QgsMessageBar.CRITICAL)
示例#11
0
    def on_btnRun_clicked(self):
        
        #Check for combo and list box selections
        if self.ui.cmbBaseLayer.count() < 1 or self.ui.cmbProcessLayer.count() < 1:
            QMessageBox.critical(self, 'Vector Geoprocessor', 'Invalid layer selection.')
            return
        if len(self.ui.listFields.selectedItems()) < 1:
            QMessageBox.critical(self, 'Vector Geoprocessor', 'Invalid field selection.')
            return            
      
        #Initializations
        self.ui.ProgressBar.setValue(0)
        self.setCursor(Qt.WaitCursor)
        data = []
            
        #Add new attributes to base layer
        bprovider = self.blayer.dataProvider()
        pprovider = self.player.dataProvider()
        pfields = pprovider.fields()
        for item in self.ui.listFields.selectedItems():
            fname = item.text()
            for fld in pfields.toList():
                if fname == fld.name():                                               
                    newfield = QgsField()
                    newfield.setName(fld.name())
                    newfield.setType(fld.type())
                    newfield.setTypeName(fld.typeName())
                    newfield.setLength(fld.length())
                    newfield.setPrecision(fld.precision())
                    newfield.setComment(fld.comment())
                    bprovider.addAttributes([newfield])            

        #Create a spatial index for faster processing
        spindex = QgsSpatialIndex()
        for pfeat in pprovider.getFeatures():
            spindex.insertFeature(pfeat)
        
        #Find the intersection of process layer features with base layer
        #To increase speed, intersect with a bounding box rectangle first
        #Then further process within the geometric shape
        #Add requested processed information to base layer                
        featreq = QgsFeatureRequest()
        bfields = bprovider.fields()
        ddic = {}
        len1 = len(self.ui.listFields.selectedItems())
        len2 = len(bfields)
        b1 = 0
        b2 = bprovider.featureCount()
        attr={}
        for bfeat in bprovider.getFeatures():
            b1+=1
            attr.clear()
            bgeom = bfeat.geometry()                                          
            intersect = spindex.intersects(bgeom.boundingBox())
            data[:] = []
            for fid in intersect:               
                pfeat = self.player.getFeatures(featreq.setFilterFid(fid)).next()
                if pfeat.geometry().intersects(bgeom) == False:
                    data.append(fid)           
            for fid in data:        
                intersect.pop(intersect.index(fid))
                              
            count = 0
            for item in self.ui.listFields.selectedItems():
                pfindx = pprovider.fieldNameIndex(item.text())
                if pfindx < 0:
                    self.setCursor(Qt.ArrowCursor)
                    QMessageBox.critical(self, 'Vector Geoprocessor', 'Processing error.')
                    return
                data[:] = []
                for fid in intersect:
                    pfeat = self.player.getFeatures(featreq.setFilterFid(fid)).next()
                    if self.oindex in [0,1,2,3,4]:
                        data.append(float(pfeat.attribute(item.text())))
                    elif self.oindex in [5,6]:
                        data.append(str(pfeat.attribute(item.text())))
                if len(data) == 0:
                    value = None
                elif self.oindex == 0: #Find mean value of points within polygons
                    value = sum(data)/float(len(data))
                elif self.oindex == 1: #Find median value of points within polygons
                    data = sorted(data)
                    lendata = len(data)
                    if lendata % 2:
                        value = data[(lendata+1)/2-1]
                    else:
                        d1 = data[lendata/2-1]
                        d2 = data[lendata/2]
                        value = (d1 + d2)/2.0
                elif self.oindex == 2: #Find maximum value of points within polygons
                    value = max(data)
                elif self.oindex == 3: #Find minimum value of points within polygons
                    value = min(data)
                elif self.oindex == 4: #Find mean value (area-weighted) of polygons within polygons
                    value = 0.0
                    totalarea = 0.0
                    for fid in intersect:
                        pfeat = self.player.getFeatures(featreq.setFilterFid(fid)).next()
                        pgeom = pfeat.geometry()
                        isect = bgeom.intersection(pgeom)
                        parea = isect.area()
                        value+=(float(pfeat.attribute(item.text())*parea))
                        totalarea+=parea
                    value = value / totalarea
                elif self.oindex == 5: #Find largest area polygon within polygons
                    data = list(set(data))  #Get unique items in data                          
                    ddic.clear()
                    for i in data:
                        ddic.update({i : 0.0})
                    for fid in intersect:
                        pfeat = self.player.getFeatures(featreq.setFilterFid(fid)).next()
                        pgeom = pfeat.geometry()
                        isect = bgeom.intersection(pgeom)
                        parea = isect.area()
                        key = str(pfeat.attribute(item.text()))
                        parea = parea + ddic[key]
                        ddic.update({key : parea})
                    parea = -1
                    for key in ddic.keys():
                        if ddic[key] > parea:
                            parea = ddic[key]
                            value = key
                elif self.oindex == 6: #Add polygon attribute to points
                    if len(data) != 1:
                        QMessageBox.warning(self, 'Vector Geoprocessor',
                                            'Point intersects more than one polygon.')
                    value = data[0]
                
                attr.update({(len2-len1+count):value})
                count+=1
            result = bprovider.changeAttributeValues({bfeat.id():attr})
            if not result:
                QMessageBox.critical(self, 'Vector Geoprocessor', 'Could not change attribute value.')
                return           
            self.ui.ProgressBar.setValue(float(b1)/float(b2) * 100.0)
            QApplication.processEvents()

        self.setCursor(Qt.ArrowCursor)
示例#12
0
    def on_btnRun_clicked(self):

        #Check for combo and list box selections
        if self.ui.cmbBaseLayer.count() < 1 or self.ui.cmbProcessLayer.count(
        ) < 1:
            QMessageBox.critical(self, 'Vector Geoprocessor',
                                 'Invalid layer selection.')
            return
        if len(self.ui.listFields.selectedItems()) < 1:
            QMessageBox.critical(self, 'Vector Geoprocessor',
                                 'Invalid field selection.')
            return

        #Initializations
        self.ui.ProgressBar.setValue(0)
        self.setCursor(Qt.WaitCursor)
        data = []

        #Add new attributes to base layer
        bprovider = self.blayer.dataProvider()
        pprovider = self.player.dataProvider()
        pfields = pprovider.fields()
        for item in self.ui.listFields.selectedItems():
            fname = item.text()
            for fld in pfields.toList():
                if fname == fld.name():
                    newfield = QgsField()
                    newfield.setName(fld.name())
                    newfield.setType(fld.type())
                    newfield.setTypeName(fld.typeName())
                    newfield.setLength(fld.length())
                    newfield.setPrecision(fld.precision())
                    newfield.setComment(fld.comment())
                    bprovider.addAttributes([newfield])

        #Create a spatial index for faster processing
        spindex = QgsSpatialIndex()
        for pfeat in pprovider.getFeatures():
            spindex.insertFeature(pfeat)

        #Find the intersection of process layer features with base layer
        #To increase speed, intersect with a bounding box rectangle first
        #Then further process within the geometric shape
        #Add requested processed information to base layer
        featreq = QgsFeatureRequest()
        bfields = bprovider.fields()
        ddic = {}
        len1 = len(self.ui.listFields.selectedItems())
        len2 = len(bfields)
        b1 = 0
        b2 = bprovider.featureCount()
        attr = {}
        for bfeat in bprovider.getFeatures():
            b1 += 1
            attr.clear()
            bgeom = bfeat.geometry()
            intersect = spindex.intersects(bgeom.boundingBox())
            data[:] = []
            for fid in intersect:
                pfeat = next(self.player.getFeatures(
                    featreq.setFilterFid(fid)))
                if pfeat.geometry().intersects(bgeom) == False:
                    data.append(fid)
            for fid in data:
                intersect.pop(intersect.index(fid))

            count = 0
            for item in self.ui.listFields.selectedItems():
                pfindx = pprovider.fieldNameIndex(item.text())
                if pfindx < 0:
                    self.setCursor(Qt.ArrowCursor)
                    QMessageBox.critical(self, 'Vector Geoprocessor',
                                         'Processing error.')
                    return
                data[:] = []
                for fid in intersect:
                    pfeat = next(
                        self.player.getFeatures(featreq.setFilterFid(fid)))
                    if self.oindex in [0, 1, 2, 3, 4]:
                        data.append(float(pfeat.attribute(item.text())))
                    elif self.oindex in [5, 6]:
                        data.append(str(pfeat.attribute(item.text())))
                if len(data) == 0:
                    value = None
                elif self.oindex == 0:  #Find mean value of points within polygons
                    value = sum(data) / float(len(data))
                elif self.oindex == 1:  #Find median value of points within polygons
                    data = sorted(data)
                    lendata = len(data)
                    if lendata % 2:
                        value = data[(lendata + 1) / 2 - 1]
                    else:
                        d1 = data[lendata / 2 - 1]
                        d2 = data[lendata / 2]
                        value = (d1 + d2) / 2.0
                elif self.oindex == 2:  #Find maximum value of points within polygons
                    value = max(data)
                elif self.oindex == 3:  #Find minimum value of points within polygons
                    value = min(data)
                elif self.oindex == 4:  #Find mean value (area-weighted) of polygons within polygons
                    value = 0.0
                    totalarea = 0.0
                    for fid in intersect:
                        pfeat = next(
                            self.player.getFeatures(featreq.setFilterFid(fid)))
                        pgeom = pfeat.geometry()
                        isect = bgeom.intersection(pgeom)
                        parea = isect.area()
                        value += (float(pfeat.attribute(item.text()) * parea))
                        totalarea += parea
                    value = value / totalarea
                elif self.oindex == 5:  #Find largest area polygon within polygons
                    data = list(set(data))  #Get unique items in data
                    ddic.clear()
                    for i in data:
                        ddic.update({i: 0.0})
                    for fid in intersect:
                        pfeat = next(
                            self.player.getFeatures(featreq.setFilterFid(fid)))
                        pgeom = pfeat.geometry()
                        isect = bgeom.intersection(pgeom)
                        parea = isect.area()
                        key = str(pfeat.attribute(item.text()))
                        parea = parea + ddic[key]
                        ddic.update({key: parea})
                    parea = -1
                    for key in list(ddic.keys()):
                        if ddic[key] > parea:
                            parea = ddic[key]
                            value = key
                elif self.oindex == 6:  #Add polygon attribute to points
                    if len(data) != 1:
                        QMessageBox.warning(
                            self, 'Vector Geoprocessor',
                            'Point intersects more than one polygon.')
                    value = data[0]

                attr.update({(len2 - len1 + count): value})
                count += 1
            result = bprovider.changeAttributeValues({bfeat.id(): attr})
            if not result:
                QMessageBox.critical(self, 'Vector Geoprocessor',
                                     'Could not change attribute value.')
                return
            self.ui.ProgressBar.setValue(float(b1) / float(b2) * 100.0)
            QApplication.processEvents()

        self.setCursor(Qt.ArrowCursor)
示例#13
0
    def CheckControlFile(self):

        #Open control file
        if not os.path.exists(self.cfilename):
            QMessageBox.critical(self, 'Simulation Controller',
                                 'Control file does not exist.')
            return 1
        else:
            self.cfile = ControlFile.ControlFile()
            ret = self.cfile.ReadFile(self.cfilename)
            if ret:
                QMessageBox.critical(self, 'Simulation Controller',
                                     'Error reading control file.')
                return 1

        #Set working directory
        if not os.path.exists(self.cfile.ModelDirectory):
            QMessageBox.critical(self, 'Simulation Controller',
                                 'Model directory does not exist.')
            return 1
        else:
            os.chdir(self.cfile.ModelDirectory)

        #Get base layer
        count = 0
        for i in self.layers:
            if i.name() == self.cfile.BaseLayer:
                self.blayer = i
                self.bprovider = self.blayer.dataProvider()
                count += 1
        if not count:  #Count==0
            QMessageBox.critical(self, 'Simulation Controller',
                                 'Base layer not found.')
            return 1
        if count > 1:
            QMessageBox.critical(
                self, 'Simulation Controller',
                'Found more than one layer with base layer name.')
            return 1

        #Check template files
        for key in sorted(self.cfile.TemplateInput.keys()):
            if not os.path.exists(self.cfile.TemplateInput[key][0]):
                QMessageBox.critical(
                    self, 'Simulation Controller', 'File does not exist: %s' %
                    self.cfile.TemplateInput[key][0])
                return 1
            else:
                f = open(self.cfile.TemplateInput[key][0], 'r')
                lines = f.readlines()
                f.close()
                if lines[0][
                        0:41] != 'Geospatial Simulation Template (GST) File':
                    QMessageBox.critical(self, 'Simulation Controller',
                                         'Check template file.')
                    return 1

        #Check for input attributes in base layer
        for key in sorted(self.cfile.AttributeCode.keys()):
            bfindx = self.bprovider.fieldNameIndex(
                self.cfile.AttributeCode[key][0])
            if bfindx < 0:
                QMessageBox.critical(
                    self, 'Simulation Controller',
                    'Missing attribute in base layer: %s' %
                    self.cfile.AttributeCode[key][0])
                return 1

        #Check instruction files
        for key in sorted(self.cfile.InstructionOutput.keys()):
            if not os.path.exists(self.cfile.InstructionOutput[key][0]):
                QMessageBox.critical(
                    self, 'Simulation Controller', 'File does not exist: %s' %
                    self.cfile.InstructionOutput[key][0])
                return 1
            else:
                f = open(self.cfile.InstructionOutput[key][0], 'r')
                lines = f.readlines()
                f.close()
                if lines[0][
                        0:44] != 'Geospatial Simulation Instruction (GSI) File':
                    QMessageBox.critical(self, 'Simulation Controller',
                                         'Check instruction file.')
                    return 1
                for line in lines[1:]:
                    line = line.split(',')
                    if len(line) < 2:
                        continue
                    found = 0
                    for key in sorted(self.cfile.AttributeType.keys()):
                        if self.cfile.AttributeType[key][0] == line[0]:
                            found = 1
                            break
                    if not found:
                        QMessageBox.critical(
                            self, 'Simulation Controller',
                            'Check control file for missing output attribute: '
                            + line[0])
                        return 1

        #Check for output attributes in base layer.  Add if missing.
        for key in sorted(self.cfile.AttributeType.keys()):
            typename = self.cfile.AttributeType[key][1].split('(')[0]
            length = self.cfile.AttributeType[key][1].split('(')[1]
            bfindx = self.bprovider.fieldNameIndex(
                self.cfile.AttributeType[key][0])
            if bfindx < 0:  #Field not found, must add it
                newfield = QgsField()
                newfield.setName(self.cfile.AttributeType[key][0])
                if typename in ['String', 'string', 'STRING']:
                    newfield.setType(QVariant.String)
                    newfield.setTypeName('String')
                    newfield.setLength(int(length.split(')')[0]))
                elif typename in ['Integer', 'integer', 'INTEGER']:
                    newfield.setType(QVariant.Int)
                    newfield.setTypeName('Integer')
                    newfield.setLength(int(length.split(')')[0]))
                elif typename in ['Real', 'real', 'REAL']:
                    newfield.setType(QVariant.Double)
                    newfield.setTypeName('Real')
                    newfield.setLength(int(length.split('.')[0]))
                    newfield.setPrecision(
                        int(length.split('.')[1].split(')')[0]))
                self.bprovider.addAttributes([newfield])

        #Enable Run button
        self.ui.btnRun.setEnabled(True)

        return 0
示例#14
0
 def test_add_attributes(self):
     field_one = QgsField('first', QVariant.String)
     field_one.setTypeName(STRING_FIELD_TYPE_NAME)
     field_two = QgsField('second', QVariant.Int)
     field_two.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_one, field_two]
     added_attributes = ProcessLayer(self.layer).add_attributes(attributes)
     expected_dict = {'first': 'first',
                      'second': 'second'}
     assert added_attributes == expected_dict
     # Let's add 2 other fields with the same names of the previous ones
     # ==> Since the names are already taken, we expect to add fields with
     # the same names plus '_1'
     field_three = QgsField('first', QVariant.String)
     field_three.setTypeName(STRING_FIELD_TYPE_NAME)
     field_four = QgsField('second', QVariant.Int)
     field_four.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_three, field_four]
     added_attributes = ProcessLayer(self.layer).add_attributes(attributes)
     expected_dict = {'first': 'first_1',
                      'second': 'second_1'}
     assert added_attributes == expected_dict
     # Let's add 2 other fields with the same names of the previous ones
     # ==> Since the names are already taken, as well as the corresponding
     # '_1' versions, we expect to add fields with the same names plus '_2'
     field_five = QgsField('first', QVariant.String)
     field_five.setTypeName(STRING_FIELD_TYPE_NAME)
     field_six = QgsField('second', QVariant.Int)
     field_six.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_five, field_six]
     added_attributes = ProcessLayer(self.layer).add_attributes(attributes)
     expected_dict = {'first': 'first_2',
                      'second': 'second_2'}
     assert added_attributes == expected_dict
示例#15
0
    def transform_attribute(self,
                            input_attr_name,
                            algorithm_name,
                            variant="",
                            inverse=False,
                            new_attr_name=None,
                            new_attr_alias=None,
                            simulate=False):
        """
        Use one of the available transformation algorithms to transform an
        attribute of the layer, and add a new attribute with the
        transformed data, or overwrite the input attribute with the results

        :param input_attr_name: name of the attribute to be transformed
        :param algorithm_name: name of the transformation algorithm
        :param variant: name of the algorithm variant
        :param inverse: boolean indicating if the inverse transformation
                        has to be performed
        :param new_attr_name: name of the target attribute that will store the
                              results of the transformation (if it is equal to
                              the input_attr_name, the attribute will be
                              overwritten)
        :param new_attr_alias: alias of the target attribute that will store
                               ther results of the transformation
        :param simulate: if True, the method will just simulate the creation
                         of the target attribute and return the name that would
                         be assigned to it
        :returns: (actual_new_attr_name, invalid_input_values)
        """
        caps = self.layer.dataProvider().capabilities()
        if not (caps & QgsVectorDataProvider.ChangeAttributeValues):
            raise TypeError('Unable to edit features of this kind of layer'
                            ' (%s). Please consider saving the layer with an'
                            ' editable format before attempting to transform'
                            ' its attributes.' % self.layer.providerType())
        # get the id of the attribute named input_attr_name
        input_attr_id = self.find_attribute_id(input_attr_name)
        overwrite = (new_attr_name is not None
                     and new_attr_name == input_attr_name)
        if simulate or not overwrite:
            # add a new attribute to store the results of the transformation
            # or simulate adding a new attribute and return the name of the
            # name that would be assigned to the new attribute if it would be
            # added.
            # NOTE: building the name of the output transformed attribute,
            #       we take into account the chosen algorithm and variant and
            #       we truncate the new name to 10 characters (max allowed for
            #       shapefiles)
            if not new_attr_name:
                if variant:
                    new_attr_name = algorithm_name[:5] + '_' + variant[:4]
                else:
                    new_attr_name = algorithm_name
            field = QgsField(new_attr_name, QVariant.Double)
            field.setTypeName(DOUBLE_FIELD_TYPE_NAME)
            if simulate:
                attr_names_dict = self.add_attributes([field],
                                                      simulate=simulate)
                # get the name actually assigned to the new attribute
                actual_new_attr_name = attr_names_dict[new_attr_name]
                return actual_new_attr_name

        # a dict will contain all the values for the chosen input attribute,
        # keeping as key, for each value, the id of the corresponding feature
        initial_dict = dict()
        request = QgsFeatureRequest().setFlags(
            QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(
                [input_attr_name], self.layer.fields())
        for feat in self.layer.getFeatures(request):
            initial_dict[feat.id()] = feat[input_attr_id]

        # get the transformation algorithm from the register
        algorithm = TRANSFORMATION_ALGS[algorithm_name]

        # transform the values in the dictionary with the chosen algorithm
        invalid_input_values = None
        try:
            transformed_dict, invalid_input_values = transform(
                initial_dict, algorithm, variant, inverse)
        except ValueError:
            raise
        except NotImplementedError:
            raise

        if overwrite:
            actual_new_attr_name = input_attr_name
            new_attr_id = input_attr_id
        else:
            attr_names_dict = self.add_attributes([field], simulate=simulate)
            # get the name actually assigned to the new attribute
            actual_new_attr_name = attr_names_dict[new_attr_name]
            # get the id of the new attribute
            new_attr_id = self.find_attribute_id(actual_new_attr_name)
        if new_attr_alias:
            with edit(self.layer):
                self.layer.setFieldAlias(new_attr_id, new_attr_alias)

        # TODO: perhaps there is a better way to get a list of feature ids.
        #       Here we are retrieving no geometries and no fields, which
        #       sounds the closest way to obtain it.
        request = QgsFeatureRequest().setFlags(
            QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(
                [], self.layer.fields())
        with edit(self.layer):
            # write transformed values
            for feat in self.layer.getFeatures(request):
                feat_id = feat.id()
                value = transformed_dict[feat_id]
                try:
                    value = float(value)
                except Exception:
                    pass
                self.layer.changeAttributeValue(feat_id, new_attr_id, value)
        return actual_new_attr_name, invalid_input_values
示例#16
0
 def test_add_attributes(self):
     field_one = QgsField('first', QVariant.String)
     field_one.setTypeName(STRING_FIELD_TYPE_NAME)
     field_two = QgsField('second', QVariant.Int)
     field_two.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_one, field_two]
     added_attributes = ProcessLayer(self.layer).add_attributes(attributes)
     expected_dict = {'first': 'first', 'second': 'second'}
     self.assertDictEqual(added_attributes, expected_dict)
     # Let's add 2 other fields with the same names of the previous ones
     # ==> Since the names are already taken, we expect to add fields with
     # the same names plus '_1'
     field_three = QgsField('first', QVariant.String)
     field_three.setTypeName(STRING_FIELD_TYPE_NAME)
     field_four = QgsField('second', QVariant.Int)
     field_four.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_three, field_four]
     added_attributes = ProcessLayer(self.layer).add_attributes(attributes)
     expected_dict = {'first': 'first_1', 'second': 'second_1'}
     self.assertEqual(added_attributes, expected_dict)
     # Let's add 2 other fields with the same names of the previous ones
     # ==> Since the names are already taken, as well as the corresponding
     # '_1' versions, we expect to add fields with the same names plus '_2'
     field_five = QgsField('first', QVariant.String)
     field_five.setTypeName(STRING_FIELD_TYPE_NAME)
     field_six = QgsField('second', QVariant.Int)
     field_six.setTypeName(INT_FIELD_TYPE_NAME)
     attributes = [field_five, field_six]
     added_attributes = ProcessLayer(self.layer).add_attributes(attributes)
     expected_dict = {'first': 'first_2', 'second': 'second_2'}
     self.assertEqual(added_attributes, expected_dict)
示例#17
0
def calculate_iri(
    iface, current_layer, project_definition, svi_attr_id, aal_field_name, discarded_feats_ids, iri_operator=None
):
    """
    Copy the AAL and calculate an IRI attribute to the current layer
    """

    # set default
    if iri_operator is None:
        iri_operator = DEFAULT_COMBINATION

    aal_weight = project_definition["children"][0]["weight"]
    svi_weight = project_definition["children"][1]["weight"]

    iri_attr_name = "IRI"
    iri_field = QgsField(iri_attr_name, QVariant.Double)
    iri_field.setTypeName(DOUBLE_FIELD_TYPE_NAME)

    attr_names = ProcessLayer(current_layer).add_attributes([iri_field])

    # get the id of the new attributes
    iri_attr_id = ProcessLayer(current_layer).find_attribute_id(attr_names[iri_attr_name])

    discarded_aal_feats_ids = []

    try:
        with LayerEditingManager(current_layer, "Add IRI", DEBUG):
            for feat in current_layer.getFeatures():
                feat_id = feat.id()
                svi_value = feat.attributes()[svi_attr_id]
                aal_value = feat[aal_field_name]
                if aal_value == QPyNullVariant(float) or feat_id in discarded_feats_ids:
                    iri_value = QPyNullVariant(float)
                    discarded_aal_feats_ids.append(feat_id)
                elif iri_operator == "Sum (simple)":
                    iri_value = svi_value + aal_value
                elif iri_operator == "Multiplication (simple)":
                    iri_value = svi_value * aal_value
                elif iri_operator == "Sum (weighted)":
                    iri_value = svi_value * svi_weight + aal_value * aal_weight
                elif iri_operator == "Multiplication (weighted)":
                    iri_value = svi_value * svi_weight * aal_value * aal_weight
                elif iri_operator == "Average (equal weights)":
                    # For "Average (equal weights)" it's equivalent to use
                    # equal weights, or to sum the indices (all weights 1)
                    # and divide by the number of indices (we use
                    # the latter solution)
                    iri_value = (svi_value + aal_value) / 2.0
                # store IRI
                current_layer.changeAttributeValue(feat_id, iri_attr_id, iri_value)
        project_definition["iri_operator"] = iri_operator
        # set the field name for the copied AAL layer
        project_definition["aal_field"] = aal_field_name
        project_definition["iri_field"] = attr_names[iri_attr_name]
        msg = (
            "The IRI has been calculated for fields containing "
            "non-NULL values and it was added to the layer as "
            "a new attribute called %s"
        ) % attr_names[iri_attr_name]
        iface.messageBar().pushMessage(tr("Info"), tr(msg), level=QgsMessageBar.INFO)
        widget = toggle_select_features_widget(
            tr("Warning"),
            tr("Invalid values were found in some features while calculating " "IRI"),
            tr("Select invalid features"),
            current_layer,
            discarded_aal_feats_ids,
            current_layer.selectedFeaturesIds(),
        )
        iface.messageBar().pushWidget(widget, QgsMessageBar.WARNING)
        return iri_attr_id

    except TypeError as e:
        current_layer.dataProvider().deleteAttributes([iri_attr_id])
        msg = "Could not calculate IRI due to data problems: %s" % e

        iface.messageBar().pushMessage(tr("Error"), tr(msg), level=QgsMessageBar.CRITICAL)