4

I have a point layer to collect plant species. My problem is I have more than 200 Latin names in a separate list to do a value relation for this attribute.

Autocomplete when I add a point works, but only with the first letters. Is it possible to have autocomplete "anywhere"?

For example, if I write vic, I would haveVicky and also Ludovic in the drop-down list? This would be useful, cause my drop-down list is a combination like this: Common name (Latin name). For some species, I remember only the Latin name, but I'm not able to find them with autocomplete.

3
  • Potentially your solution is in this question: gis.stackexchange.com/questions/407104/… I had a similar problem a while ago.
    – Geographos
    Mar 16, 2022 at 9:39
  • Not sure to understand... I don't want to add a value in a list? I just want to find in a list of more than 200 names a solution to find the correct specie with autocomplet. If I tape "Picea", I have a list of all the "Picea" in my drop-down list...OK! But I can't type "cea" to have all the species than contain "cea" in there name.
    – katagena
    Mar 16, 2022 at 10:01
  • I did a separate plugin which used a json file relation like layer_name - field_name - list_of_values. Plugin has a search line, values combobox and button to change value in field. By using python you can filter out values from list and set a new value in field Mar 26, 2022 at 13:50

3 Answers 3

5
+50

You cannot search for part of a value with the Value Relation widget. This is currently not supported. See feature request here.

4

It works for me, if I use the “normal” relations which are found under project properties. In your case, when combining English and scientific names, I would recommend you to create a third row in the species list, which aggregates both names (separated with a | or similar) and use this row as the display expression in the relation reference. This way you should be able to type whatever part of the names you want.

1

You can try this small plugin in python console. Let there be a layer called polygons. This layer have some fields including name which should contain a list of specific values.

When you launch the script you will see this thing:

enter image description here

As I said in a comment section of question: there is a user input line, combobxes (fields, values) and button to set a new value.

Once you select a new layer, field+values comboboxes are changing. Once you change field, a value list is updating accroding to its existance in sample dict. And once you enter something in a line box, values box will filter its content to the closest values to user print.

enter image description here

After you press button Set value, script will update all selected features' field according to what you select in values combobox. If nothing was found in combobox, field attribute will be updated with a user printed value.

You can change values_dict according to your layer and test it.

# sample values dict
values_dict = {
    "polygons":{
        "name": [
            "Panthera pardus orientalis",
            "Diceros bicornis", 
            "Pongo pygmaeus", 
            "Gorilla gorilla diehli",
            "Eretmochelys imbricata", 
            "Rhinoceros sondaicus",
            "Crocodile"
        ]
    }
}

class filler(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
        self.grid = QGridLayout()
        self.grid.setSpacing(10)
        
        # variables
        self.current_layer = None
        self.fields_data = []
        self.values_data = []

        # widgets
        self.line_search = QLineEdit()
        self.combo_fields= QComboBox()
        self.combo_values= QComboBox()
        self.value_change = QPushButton('Set value')
        
        # gui setup
        self.setLayout(self.grid)
        self.grid.addWidget(self.line_search, 0, 1, 1, 5)
        self.grid.addWidget(self.combo_fields, 2, 1, 1, 2)
        self.grid.addWidget(self.combo_values, 2, 3, 1, 3)
        self.grid.addWidget(self.value_change, 3, 1, 1, 5)

        # actions
        iface.currentLayerChanged.connect(self.get_active_layer)
        self.combo_fields.currentIndexChanged.connect(self.update_field_data)
        self.line_search.textChanged.connect(self.filter_values)
        self.value_change.clicked.connect(self.calculate)

        self.get_active_layer()
        self.show()


    def get_active_layer(self):
        # get an active layer and load its fields and related values
        if iface.activeLayer().type() == QgsVectorLayer.VectorLayer:
            self.current_layer = iface.activeLayer()
            self.fields_data = self.current_layer.fields().names()
            self.combo_fields.clear()
            self.combo_fields.addItems(self.fields_data)
            self.update_field_data()
            
    
    def update_field_data(self):
        # get field values load in combobox
        cur_index = self.combo_fields.currentIndex()
        cur_index = cur_index if cur_index!=-1 else 0
        self.combo_values.clear()
        field_data = values_dict.get(self.current_layer.name(), {})
        self.values_data = field_data.get(self.fields_data[cur_index],[])
        self.combo_values.addItems(self.values_data)
    

    def filter_values(self):
        # filtering values by user text
        value_str = self.line_search.text().lower()
        if not value_str:
            self.combo_values.clear()
            self.combo_values.addItems(self.values_data)
            return
        
        # dict like {"sample data value": ["Sample Data Value": ["sample", "data", "value"]]}
        splitted_values = {n.lower(): [n, n.lower().split()] for n in self.values_data if value_str in n.lower()}
        
        # sorting findings
        # if init value starts with user value => wrd_index is 1
        # if part of init value starts with user value => wrd_index is 2
        # if user value within init value => wrd_index is 3
        sorted_data = {}
        for key, value in splitted_values.items():
            if key.startswith(value_str):
                wrd_index = 1
            starting_v = [v.startswith(value_str) for v in value[1]]
            if any(n for n in starting_v):
                wrd_index = 2
            else:
                wrd_index = 3
            sorted_data[value[0]] = wrd_index
        
        # sorting findings by wrd_index and loading them into combobox
        sorted_findings = [v[0] for v in list(sorted(sorted_data.items(), key = lambda x: x[1]))]
        self.combo_values.clear()
        self.combo_values.addItems(sorted_findings)


    def calculate(self):
        # updating features with new value
        # works only if features are selected

        list_selected = self.current_layer.selectedFeatureIds()
        if not list_selected:
            QMessageBox.information(None, "Warning", 'Select features to update') 
            return
        
        # if nothing was found the user input is taken
        new_value = self.combo_values.currentText() if self.combo_values.currentText() else self.line_search.text()
        field_id = self.combo_fields.currentIndex()
        self.current_layer.startEditing()
        for fid in list_selected:
            self.current_layer.changeAttributeValue(fid, field_id, new_value)


app = filler()
2
  • Not sure to see how to use it when you digitalize a lot of feature with around 12 different attributes?!?
    – katagena
    Mar 26, 2022 at 22:46
  • @katagena scroll through each field and type text that will be searched in values dict. You can extend dict with another fields and values. I suppose it could be the way faster than manual typing in each field. Mar 27, 2022 at 8:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.