-
Notifications
You must be signed in to change notification settings - Fork 0
/
featureselection_widget.py
171 lines (147 loc) · 7.24 KB
/
featureselection_widget.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import os
from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt
from qgis.PyQt import QtWidgets
from qgis.PyQt.QtWidgets import QListWidget, QListWidgetItem, QPushButton
from qgis.gui import QgsFieldComboBox
from qgis.core import QgsApplication, QgsVectorLayer, QgsFeatureIterator
FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'featureselection_widget.ui'))
class FeatureSelectionWidget(QtWidgets.QWidget, FORM_CLASS):
selectAllBtn: QPushButton
featureLabelCb: QgsFieldComboBox
def __init__(self, parent=None):
"""Constructor."""
super().__init__(parent)
self.setupUi(self)
self._layer: QgsVectorLayer = None
self._featureLabel = None
self.selectAllBtn.setAutoDefault(False)
self.selectSingleBtn.setAutoDefault(False)
self.unselectSingleBtn.setAutoDefault(False)
self.unselectAllBtn.setAutoDefault(False)
self.featureLabelCb.fieldChanged.connect(self._layerFieldChanged)
self.selectAllBtn.clicked.connect(self.move_all_to_selection)
self.selectSingleBtn.clicked.connect(self.move_selected_to_selection)
self.unselectSingleBtn.clicked.connect(self.remove_selected_from_selection)
self.unselectAllBtn.clicked.connect(self.remove_all_from_selection)
def setLayer(self, layer: QgsVectorLayer = None):
self._layer = layer
self.featureLabelCb.setLayer(layer);
if self._layer is None:
self.featureLabelCb.clear()
self.featureLabelCb.setDisabled(True)
self._featureLabel = None
else:
self.featureLabelCb.setDisabled(False)
self.featureLabelCb.setCurrentIndex(0)
self._featureLabel = self.featureLabelCb.currentField()
def _layerFieldChanged(self, label: str):
self._featureLabel = label
self.rename_items(self._layer, self._featureLabel, self.availableFeaturesLw)
self.rename_items(self._layer, self._featureLabel, self.selectedFeaturesLw)
def removeMarked(self):
self.select_features_on_canvas([])
def removeFeatures(self):
self.availableFeaturesLw.clear()
self.selectedFeaturesLw.clear()
def populateFeatures(self):
"""Populates the features list from the faceted layer.
If more than 1000 features are currently visible, the list is truncated
and a placeholder "..." is appended to ensure performance.
"""
self.availableFeaturesLw.clear()
self.selectedFeaturesLw.clear()
features: QgsFeatureIterator = self._layer.getFeatures()
too_many_hits = False
NUM_MAX_HITS = 1000
if features:
# safe_disconnect(self.dlg.lw_available.itemSelectionChanged, self.highlight_features)
for i, feature in enumerate(features):
item = QListWidgetItem() # necessary for adding data to the items
item.setText("{}".format(feature[self._featureLabel]))
item.setData(Qt.UserRole, feature.id()) # whatever the UserRole is...
self.availableFeaturesLw.addItem(item)
if i == NUM_MAX_HITS: # TODO make proper "constant" and put it somewhere else
too_many_hits = True
break
self.availableFeaturesLw.sortItems()
if too_many_hits:
placeholder_item = QListWidgetItem(f"... (more than {NUM_MAX_HITS}, please further narrow your filter)")
placeholder_item.setFlags(placeholder_item.flags() & ~Qt.ItemIsEnabled)
self.availableFeaturesLw.addItem(placeholder_item)
else:
self.removeFeatures()
def move_all_to_selection(self):
"""Moves *all* available Features into selection."""
self.move_all_items(self.availableFeaturesLw, self.selectedFeaturesLw)
self.select_features_on_canvas(self.selected_features_ids())
def move_selected_to_selection(self):
"""Moves all *selected* available Features into selection."""
self.move_selected_items(self.availableFeaturesLw, self.selectedFeaturesLw)
self.select_features_on_canvas(self.selected_features_ids())
def remove_all_from_selection(self):
"""Removes *all* Features from current selection."""
self.move_all_items(self.selectedFeaturesLw, self.availableFeaturesLw)
self._layer.removeSelection()
def remove_selected_from_selection(self):
"""Removes all *selected* Features from current selection."""
self.move_selected_items(self.selectedFeaturesLw, self.availableFeaturesLw)
self.select_features_on_canvas(self.selected_features_ids())
@staticmethod
def rename_items(layer: QgsVectorLayer, field: str, listWidget: QListWidget):
# dirty solution ?
list = []
for idx in reversed(range(listWidget.count())):
item: QListWidgetItem = listWidget.takeItem(idx)
feature = layer.getFeature(item.data(Qt.UserRole))
item.setText("{}".format(feature[field]))
list.append(item)
listWidget.clear()
for item in reversed(list):
listWidget.addItem(item)
@staticmethod
def move_all_items(listWidget_a, listWidget_b):
"""Moves all items from one ListWidget to another.
Args:
listWidget_a: The originating ListWidget
listWidget_b: The target ListWidget
"""
# via https://stackoverflow.com/questions/9713298/pyqt4-move-items-from-one-qlistwidget-to-another
# sort rows in descending order in order to compensate shifting due to takeItem
# this also applies to the other methods in this context
for row in reversed(range(listWidget_a.count())):
listWidget_b.addItem(listWidget_a.takeItem(row))
listWidget_b.sortItems()
# force update, otherwise sometimes it took SECONDS, at least in 3.4
# FIXME investigate reason and implement proper fix
QgsApplication.processEvents()
@staticmethod
def move_selected_items(listWidget_a, listWidget_b):
"""Moves selected items from one ListWidget to another.
Args:
listWidget_a: The originating ListWidget
listWidget_b: The target ListWidget
"""
rows = sorted([index.row() for index in listWidget_a.selectedIndexes()], reverse=True)
for row in rows:
listWidget_b.addItem(listWidget_a.takeItem(row))
listWidget_b.sortItems()
# force update, otherwise sometimes it took SECONDS, at least in 3.4
# FIXME investigate reason and implement proper fix
QgsApplication.processEvents()
def selected_features_ids(self):
"""Returns the IDs of the features currently selected by the user.
Returns:
A list of feature IDs
"""
ids = []
for row in range(self.selectedFeaturesLw.count()):
item = self.selectedFeaturesLw.item(row)
ids.append(item.data(Qt.UserRole))
return ids
def select_features_on_canvas(self, ids):
"""Select the features referenced by the supplied IDs on the canvas."""
# only remove selection if exists, potentially saves a costly redraw
if self._layer.selectedFeatures():
self._layer.removeSelection()
self._layer.select(ids)