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
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
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')
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
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
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
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
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
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)
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)
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)
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
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
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
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)
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)