BERATools 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. beratools/__init__.py +1 -7
  2. beratools/core/algo_centerline.py +491 -351
  3. beratools/core/algo_common.py +497 -0
  4. beratools/core/algo_cost.py +192 -0
  5. beratools/core/{dijkstra_algorithm.py → algo_dijkstra.py} +503 -460
  6. beratools/core/algo_footprint_rel.py +577 -0
  7. beratools/core/algo_line_grouping.py +944 -0
  8. beratools/core/algo_merge_lines.py +214 -0
  9. beratools/core/algo_split_with_lines.py +304 -0
  10. beratools/core/algo_tiler.py +428 -0
  11. beratools/core/algo_vertex_optimization.py +469 -0
  12. beratools/core/constants.py +52 -86
  13. beratools/core/logger.py +76 -85
  14. beratools/core/tool_base.py +196 -133
  15. beratools/gui/__init__.py +11 -15
  16. beratools/gui/{beratools.json → assets/beratools.json} +2185 -2300
  17. beratools/gui/batch_processing_dlg.py +513 -463
  18. beratools/gui/bt_data.py +481 -487
  19. beratools/gui/bt_gui_main.py +710 -691
  20. beratools/gui/main.py +26 -0
  21. beratools/gui/map_window.py +162 -146
  22. beratools/gui/tool_widgets.py +725 -493
  23. beratools/tools/Beratools_r_script.r +1120 -1120
  24. beratools/tools/Ht_metrics.py +116 -116
  25. beratools/tools/__init__.py +7 -7
  26. beratools/tools/batch_processing.py +136 -132
  27. beratools/tools/canopy_threshold_relative.py +672 -670
  28. beratools/tools/canopycostraster.py +222 -222
  29. beratools/tools/centerline.py +136 -176
  30. beratools/tools/common.py +857 -885
  31. beratools/tools/fl_regen_csf.py +428 -428
  32. beratools/tools/forest_line_attributes.py +408 -408
  33. beratools/tools/line_footprint_absolute.py +213 -363
  34. beratools/tools/line_footprint_fixed.py +436 -282
  35. beratools/tools/line_footprint_functions.py +733 -720
  36. beratools/tools/line_footprint_relative.py +73 -64
  37. beratools/tools/line_grouping.py +45 -0
  38. beratools/tools/ln_relative_metrics.py +615 -615
  39. beratools/tools/r_cal_lpi_elai.r +24 -24
  40. beratools/tools/r_generate_pd_focalraster.r +100 -100
  41. beratools/tools/r_interface.py +79 -79
  42. beratools/tools/r_point_density.r +8 -8
  43. beratools/tools/rpy_chm2trees.py +86 -86
  44. beratools/tools/rpy_dsm_chm_by.py +81 -81
  45. beratools/tools/rpy_dtm_by.py +63 -63
  46. beratools/tools/rpy_find_cellsize.py +43 -43
  47. beratools/tools/rpy_gnd_csf.py +74 -74
  48. beratools/tools/rpy_hummock_hollow.py +85 -85
  49. beratools/tools/rpy_hummock_hollow_raster.py +71 -71
  50. beratools/tools/rpy_las_info.py +51 -51
  51. beratools/tools/rpy_laz2las.py +40 -40
  52. beratools/tools/rpy_lpi_elai_lascat.py +466 -466
  53. beratools/tools/rpy_normalized_lidar_by.py +56 -56
  54. beratools/tools/rpy_percent_above_dbh.py +80 -80
  55. beratools/tools/rpy_points2trees.py +88 -88
  56. beratools/tools/rpy_vegcoverage.py +94 -94
  57. beratools/tools/tiler.py +48 -206
  58. beratools/tools/tool_template.py +69 -54
  59. beratools/tools/vertex_optimization.py +61 -620
  60. beratools/tools/zonal_threshold.py +144 -144
  61. beratools-0.2.2.dist-info/METADATA +108 -0
  62. beratools-0.2.2.dist-info/RECORD +74 -0
  63. {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/WHEEL +1 -1
  64. {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/licenses/LICENSE +22 -22
  65. beratools/gui/cli.py +0 -18
  66. beratools/gui/gui.json +0 -8
  67. beratools/gui_tk/ASCII Banners.txt +0 -248
  68. beratools/gui_tk/__init__.py +0 -20
  69. beratools/gui_tk/beratools_main.py +0 -515
  70. beratools/gui_tk/bt_widgets.py +0 -442
  71. beratools/gui_tk/cli.py +0 -18
  72. beratools/gui_tk/img/BERALogo.png +0 -0
  73. beratools/gui_tk/img/closed.gif +0 -0
  74. beratools/gui_tk/img/closed.png +0 -0
  75. beratools/gui_tk/img/open.gif +0 -0
  76. beratools/gui_tk/img/open.png +0 -0
  77. beratools/gui_tk/img/tool.gif +0 -0
  78. beratools/gui_tk/img/tool.png +0 -0
  79. beratools/gui_tk/main.py +0 -14
  80. beratools/gui_tk/map_window.py +0 -144
  81. beratools/gui_tk/runner.py +0 -1481
  82. beratools/gui_tk/tooltip.py +0 -55
  83. beratools/third_party/pyqtlet2/__init__.py +0 -9
  84. beratools/third_party/pyqtlet2/leaflet/__init__.py +0 -26
  85. beratools/third_party/pyqtlet2/leaflet/control/__init__.py +0 -6
  86. beratools/third_party/pyqtlet2/leaflet/control/control.py +0 -59
  87. beratools/third_party/pyqtlet2/leaflet/control/draw.py +0 -52
  88. beratools/third_party/pyqtlet2/leaflet/control/layers.py +0 -20
  89. beratools/third_party/pyqtlet2/leaflet/core/Parser.py +0 -24
  90. beratools/third_party/pyqtlet2/leaflet/core/__init__.py +0 -2
  91. beratools/third_party/pyqtlet2/leaflet/core/evented.py +0 -180
  92. beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +0 -5
  93. beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +0 -34
  94. beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +0 -1
  95. beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +0 -30
  96. beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +0 -18
  97. beratools/third_party/pyqtlet2/leaflet/layer/layer.py +0 -105
  98. beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +0 -45
  99. beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +0 -1
  100. beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +0 -91
  101. beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +0 -2
  102. beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +0 -4
  103. beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +0 -16
  104. beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +0 -5
  105. beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +0 -15
  106. beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +0 -18
  107. beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +0 -5
  108. beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +0 -14
  109. beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +0 -18
  110. beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +0 -14
  111. beratools/third_party/pyqtlet2/leaflet/map/__init__.py +0 -1
  112. beratools/third_party/pyqtlet2/leaflet/map/map.py +0 -220
  113. beratools/third_party/pyqtlet2/mapwidget.py +0 -45
  114. beratools/third_party/pyqtlet2/web/custom.js +0 -43
  115. beratools/third_party/pyqtlet2/web/map.html +0 -23
  116. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
  117. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
  118. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
  119. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
  120. beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
  121. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +0 -656
  122. beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +0 -6
  123. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +0 -14
  124. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +0 -4
  125. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +0 -22
  126. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +0 -43
  127. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +0 -20
  128. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
  129. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
  130. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
  131. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
  132. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
  133. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
  134. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
  135. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +0 -156
  136. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +0 -10
  137. beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +0 -10
  138. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +0 -22
  139. beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +0 -57
  140. beratools/tools/forest_line_ecosite.py +0 -216
  141. beratools/tools/lapis_all.py +0 -103
  142. beratools/tools/least_cost_path_from_chm.py +0 -152
  143. beratools-0.2.0.dist-info/METADATA +0 -63
  144. beratools-0.2.0.dist-info/RECORD +0 -142
  145. /beratools/gui/{img → assets}/BERALogo.png +0 -0
  146. /beratools/gui/{img → assets}/closed.gif +0 -0
  147. /beratools/gui/{img → assets}/closed.png +0 -0
  148. /beratools/{gui_tk → gui/assets}/gui.json +0 -0
  149. /beratools/gui/{img → assets}/open.gif +0 -0
  150. /beratools/gui/{img → assets}/open.png +0 -0
  151. /beratools/gui/{img → assets}/tool.gif +0 -0
  152. /beratools/gui/{img → assets}/tool.png +0 -0
  153. {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/entry_points.txt +0 -0
@@ -1,493 +1,725 @@
1
- import os
2
- import sys
3
- from PyQt5.QtWidgets import (
4
- QApplication,
5
- QLineEdit,
6
- QFileDialog,
7
- QComboBox,
8
- QWidget,
9
- QPushButton,
10
- QLabel,
11
- QSlider,
12
- QMessageBox,
13
- QStyleOptionSlider,
14
- QStyle,
15
- QToolTip,
16
- QAbstractSlider,
17
- QHBoxLayout,
18
- QVBoxLayout,
19
- QSpinBox,
20
- QDoubleSpinBox
21
- )
22
-
23
- from PyQt5.QtCore import pyqtSignal, Qt, QPoint
24
- from re import search
25
-
26
- from common import *
27
-
28
-
29
- class ToolWidgets(QWidget):
30
- signal_save_tool_params = pyqtSignal(object)
31
-
32
- def __init__(self, tool_name, tool_args, show_advanced, parent=None):
33
- super(ToolWidgets, self).__init__(parent)
34
-
35
- self.tool_name = tool_name
36
- self.show_advanced = show_advanced
37
- self.current_tool_api = ''
38
- self.widget_list = []
39
- self.setWindowTitle("Tool widgets")
40
-
41
- self.create_widgets(tool_args)
42
- layout = QVBoxLayout()
43
-
44
- for item in self.widget_list:
45
- layout.addWidget(item)
46
-
47
- self.save_button = QPushButton('Save Parameters')
48
- self.save_button.clicked.connect(self.save_tool_parameters)
49
- self.save_button.setFixedSize(200, 50)
50
- layout.addSpacing(20)
51
- self.setLayout(layout)
52
-
53
- def get_widgets_arguments(self):
54
- args = {}
55
- param_missing = False
56
- for widget in self.widget_list:
57
- v = widget.get_value()
58
- if v and len(v) == 2:
59
- args[v[0]] = v[1]
60
- else:
61
- print(f'[Missing argument]: {widget.name} parameter not specified.', 'missing')
62
- param_missing = True
63
-
64
- if param_missing:
65
- args = None
66
-
67
- return args
68
-
69
- def create_widgets(self, tool_args):
70
- param_num = 0
71
- for p in tool_args:
72
- json_str = json.dumps(p, sort_keys=True, indent=2, separators=(',', ': '))
73
- pt = p['parameter_type']
74
- widget = None
75
-
76
- if 'ExistingFileOrFloat' in pt:
77
- widget = FileOrFloat(json_str, None)
78
- param_num = param_num + 1
79
- elif 'ExistingFile' in pt or 'NewFile' in pt or 'Directory' in pt:
80
- widget = FileSelector(json_str, None)
81
- param_num = param_num + 1
82
- elif 'FileList' in pt:
83
- widget = MultiFileSelector(json_str, None)
84
- param_num = param_num + 1
85
- elif 'Boolean' in pt:
86
- widget = BooleanInput(json_str)
87
- param_num = param_num + 1
88
- elif 'OptionList' in pt:
89
- widget = OptionsInput(json_str)
90
- param_num = param_num + 1
91
- elif ('Float' in pt or 'Integer' in pt or
92
- 'Text' in pt or 'String' in pt or 'StringOrNumber' in pt or
93
- 'StringList' in pt or 'VectorAttributeField' in pt):
94
- widget = DataInput(json_str)
95
- param_num = param_num + 1
96
- else:
97
- msg_box = QMessageBox()
98
- msg_box.setIcon(QMessageBox.Warning)
99
- msg_box.setText("Unsupported parameter type: {}.".format(pt))
100
- msg_box.exec()
101
-
102
- param_value = None
103
- if 'saved_value' in p.keys():
104
- param_value = p['saved_value']
105
- if param_value is None:
106
- param_value = p['default_value']
107
- if param_value is not None:
108
- if type(widget) is OptionsInput:
109
- widget.value = param_value
110
- elif widget:
111
- widget.value = param_value
112
- else:
113
- print('No default value found: {}'.format(p['name']))
114
-
115
- # hide optional widgets
116
- if widget:
117
- if widget.optional and widget.label:
118
- widget.label.setStyleSheet("QLabel { background-color : transparent; color : blue; }")
119
-
120
- if not self.show_advanced and widget.optional:
121
- widget.hide()
122
-
123
- self.widget_list.append(widget)
124
-
125
- def update_widgets(self, values_dict):
126
- for key, value in values_dict.items():
127
- for item in self.widget_list:
128
- if key == item.flag:
129
- item.set_value(value)
130
-
131
- def save_tool_parameters(self):
132
- params = {}
133
- for item in self.widget_list:
134
- if item.flag:
135
- params[item.flag] = item.get_value()
136
-
137
- self.signal_save_tool_params.emit(params)
138
-
139
- def load_default_args(self):
140
- for item in self.widget_list:
141
- item.set_default_value()
142
-
143
-
144
- class FileSelector(QWidget):
145
- def __init__(self, json_str, parent=None):
146
- super(FileSelector, self).__init__(parent)
147
-
148
- # first make sure that the json data has the correct fields
149
- params = json.loads(json_str)
150
- self.name = params['name']
151
- self.description = params['description']
152
- self.flag = params['flag']
153
- self.parameter_type = params['parameter_type']
154
- self.file_type = ""
155
- if "ExistingFile" in self.parameter_type:
156
- self.file_type = params['parameter_type']['ExistingFile']
157
- elif "NewFile" in self.parameter_type:
158
- self.file_type = params['parameter_type']['NewFile']
159
- self.optional = params['optional']
160
-
161
- self.default_value = params['default_value']
162
- self.value = self.default_value
163
- if 'saved_value' in params.keys():
164
- self.value = params['saved_value']
165
-
166
- self.layout = QHBoxLayout()
167
- self.label = QLabel(self.name)
168
- self.label.setMinimumWidth(BT_LABEL_MIN_WIDTH)
169
- self.in_file = QLineEdit(self.value)
170
- self.btn_select = QPushButton("...")
171
- self.btn_select.clicked.connect(self.select_file)
172
- self.layout.addWidget(self.label)
173
- self.layout.addWidget(self.in_file)
174
- self.layout.addWidget(self.btn_select)
175
-
176
- self.setLayout(self.layout)
177
-
178
- def select_file(self):
179
- try:
180
- dialog = QFileDialog(self)
181
- dialog.setViewMode(QFileDialog.Detail)
182
- dialog.setDirectory(str(Path(self.value).parent))
183
- dialog.selectFile(Path(self.value).name)
184
- result = None
185
- file_names = None
186
-
187
- if "Directory" in self.parameter_type:
188
- dialog.setFileMode(QFileDialog.Directory)
189
-
190
- if dialog.exec_():
191
- folder_name = dialog.selectedFiles()
192
- print(folder_name)
193
- self.set_value(folder_name)
194
- elif "ExistingFile" in self.parameter_type or "NewFile" in self.parameter_type:
195
- file_types = "All files '*.*')"
196
- if 'RasterAndVector' in self.file_type:
197
- file_types = """Shapefiles (*.shp);;
198
- Raster files (*.dep *.tif *.tiff *.bil *.flt *.sdat *.rdc *.asc *grd)"""
199
- elif 'Raster' in self.file_type:
200
- file_types = """Tiff raster files (*.tif *.tiff);;
201
- Other raster files (*.dep *.bil *.flt *.sdat *.rdc *.asc *.grd)"""
202
- elif 'Lidar' in self.file_type:
203
- file_types = "LiDAR files (*.las *.zlidar *.laz *.zip)"
204
- elif 'Vector' in self.file_type:
205
- file_types = "Shapefiles (*.shp)"
206
- elif 'Text' in self.file_type:
207
- file_types = "Text files (*.txt);; all files (*.*)"
208
- elif 'Csv' in self.file_type:
209
- file_types = "CSC files (*.csv);; all files (*.*)"
210
- elif 'Dat' in self.file_type:
211
- file_types = "Binary data files (*.dat);; all files (*.*)"
212
- elif 'Html' in self.file_type:
213
- file_types = "HTML files (*.html)"
214
- elif 'json' in self.file_type or 'JSON' in self.file_type:
215
- file_types = "JSON files (*.json)"
216
-
217
- dialog.setNameFilter(file_types)
218
-
219
- if "ExistingFile" in self.parameter_type:
220
- dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
221
- else:
222
- dialog.setFileMode(QFileDialog.FileMode.AnyFile)
223
-
224
- if dialog.exec_():
225
- file_names = dialog.selectedFiles()
226
-
227
- if not file_names:
228
- return
229
-
230
- if len(file_names) == 0:
231
- print('No file(s) selected.')
232
-
233
- if file_names[0] == '':
234
- print('File name not valid.')
235
- return
236
-
237
- # append suffix when not
238
- # TODO: more consideration for multiple formats
239
- result = file_names[0]
240
- file_path = Path(result)
241
- if result != '':
242
- break_loop = False
243
- selected_filters = self.get_file_filter_list(dialog.selectedNameFilter())
244
-
245
- if file_path.suffix not in selected_filters:
246
- if selected_filters[0] != '.*':
247
- file_path = file_path.with_suffix(selected_filters[0])
248
-
249
- result = file_path.as_posix()
250
- self.set_value(result)
251
- except Exception as e:
252
- print(e)
253
-
254
- t = "file"
255
- if self.parameter_type == "Directory":
256
- t = "directory"
257
-
258
- msg_box = QMessageBox()
259
- msg_box.setIcon(QMessageBox.Warning)
260
- msg_box.setText("Could not find {}".format(t))
261
- msg_box.exec()
262
-
263
- @staticmethod
264
- def get_file_filter_list(filter_str):
265
- """
266
- Extract filters out of full filter string, split int list and replace first '*'
267
- Result: ['.shp', '.*']
268
- """
269
- filter_list = search('\((.+?)\)', filter_str).group(1).split(' ')
270
- filter_list = [item.replace('*', '', 1) for item in filter_list if item != '']
271
- return filter_list
272
-
273
- def get_value(self):
274
- return self.flag, self.value
275
-
276
- def set_value(self, value):
277
- if type(value) is list:
278
- if len(value) > 0:
279
- value = value[0]
280
-
281
- self.value = value
282
- self.in_file.setText(self.value)
283
- self.in_file.setToolTip(self.value)
284
-
285
- def set_default_value(self):
286
- self.value = self.default_value
287
- self.in_file.setText(self.value)
288
-
289
-
290
- class FileOrFloat(QWidget):
291
- def __init__(self, json_str, parent=None):
292
- super(FileOrFloat, self).__init__(parent)
293
- pass
294
-
295
-
296
- class MultiFileSelector(QWidget):
297
- def __init__(self, json_str, parent=None):
298
- super(MultiFileSelector, self).__init__(parent)
299
- pass
300
-
301
-
302
- class BooleanInput(QWidget):
303
- def __init__(self, json_str, parent=None):
304
- super(BooleanInput, self).__init__(parent)
305
- pass
306
-
307
-
308
- class OptionsInput(QWidget):
309
- def __init__(self, json_str, parent=None):
310
- super(OptionsInput, self).__init__(parent)
311
-
312
- # first make sure that the json data has the correct fields
313
- params = json.loads(json_str)
314
- self.name = params['name']
315
- self.description = params['description']
316
- self.flag = params['flag']
317
- self.parameter_type = params['parameter_type']
318
- self.optional = params['optional']
319
- self.data_type = params['data_type']
320
-
321
- self.default_value = str(params['default_value'])
322
- self.value = self.default_value
323
- if 'saved_value' in params.keys():
324
- self.value = params['saved_value']
325
-
326
- self.label = QLabel(self.name)
327
- self.label.setMinimumWidth(BT_LABEL_MIN_WIDTH)
328
- self.combobox = QComboBox()
329
- self.combobox.currentIndexChanged.connect(self.selection_change)
330
-
331
- i = 1
332
- default_index = -1
333
- self.option_list = params['parameter_type']['OptionList']
334
- if self.option_list:
335
- self.option_list = [str(item) for item in self.option_list] # convert to strings
336
- values = ()
337
- for v in self.option_list:
338
- values += (v,)
339
- if v == str(self.value):
340
- default_index = i - 1
341
- i = i + 1
342
-
343
- self.combobox.addItems(self.option_list)
344
- self.combobox.setCurrentIndex(default_index)
345
-
346
- self.layout = QHBoxLayout()
347
- self.layout.addWidget(self.label)
348
- self.layout.addWidget(self.combobox)
349
- self.setLayout(self.layout)
350
-
351
- def selection_change(self, i):
352
- self.value = self.option_list[i]
353
-
354
- def set_value(self, value):
355
- self.value = value
356
- for v in self.option_list:
357
- if value == v:
358
- self.combobox.setCurrentIndex(self.option_list.index(v))
359
-
360
- def set_default_value(self):
361
- self.value = self.default_value
362
- for v in self.option_list:
363
- if self.value == v:
364
- self.combobox.setCurrentIndex(self.option_list.index(v))
365
-
366
- def get_value(self):
367
- return self.flag, self.value
368
-
369
-
370
- class DataInput(QWidget):
371
- def __init__(self, json_str, parent=None):
372
- super(DataInput, self).__init__(parent)
373
-
374
- # first make sure that the json data has the correct fields
375
- params = json.loads(json_str)
376
- self.name = params['name']
377
- self.description = params['description']
378
- self.flag = params['flag']
379
- self.parameter_type = params['parameter_type']
380
- self.optional = params['optional']
381
-
382
- self.default_value = params['default_value']
383
- self.value = self.default_value
384
- if 'saved_value' in params.keys():
385
- self.value = params['saved_value']
386
-
387
- self.label = QLabel(self.name)
388
- self.label.setMinimumWidth(BT_LABEL_MIN_WIDTH)
389
- self.data_input = None
390
-
391
- if "Integer" in self.parameter_type:
392
- self.data_input = QSpinBox()
393
- elif "Float" in self.parameter_type or "Double" in self.parameter_type:
394
- self.data_input = QDoubleSpinBox()
395
-
396
- if self.data_input:
397
- self.data_input.setValue(self.value)
398
-
399
- self.data_input.valueChanged.connect(self.update_value)
400
-
401
- self.layout = QHBoxLayout()
402
- self.layout.addWidget(self.label)
403
- self.layout.addWidget(self.data_input)
404
- self.setLayout(self.layout)
405
-
406
- def update_value(self):
407
- self.value = self.data_input.value()
408
-
409
- def get_value(self):
410
- v = self.value
411
- if v is not None:
412
- if "Integer" in self.parameter_type:
413
- return self.flag, int(self.value)
414
- elif "Float" in self.parameter_type:
415
- return self.flag, float(self.value)
416
- elif "Double" in self.parameter_type:
417
- return self.flag, float(self.value)
418
- else: # String or StringOrNumber types
419
- return self.flag, self.value
420
- else:
421
- if not self.optional:
422
- msg_box = QMessageBox()
423
- msg_box.setIcon(QMessageBox.Warning)
424
- msg_box.setText("Unspecified non-optional parameter {}.".format(self.flag))
425
- msg_box.exec()
426
-
427
- return None
428
-
429
- def set_value(self, value):
430
- if self.data_input:
431
- self.data_input.setValue(value)
432
- self.update_value()
433
-
434
- def set_default_value(self):
435
- if self.data_input:
436
- self.data_input.setValue(self.default_value)
437
- self.update_value()
438
-
439
-
440
- class DoubleSlider(QSlider):
441
- # create our signal that we can connect to if necessary
442
- doubleValueChanged = pyqtSignal(float)
443
-
444
- def __init__(self, decimals=3, *args, **kargs):
445
- super(DoubleSlider, self).__init__(Qt.Horizontal)
446
- self._multi = 10 ** decimals
447
-
448
- self.opt = QStyleOptionSlider()
449
- self.initStyleOption(self.opt)
450
-
451
- self.valueChanged.connect(self.emit_double_value_changed)
452
-
453
- def emit_double_value_changed(self):
454
- value = float(super(DoubleSlider, self).value()) / self._multi
455
- self.doubleValueChanged.emit(value)
456
-
457
- def value(self):
458
- return float(super(DoubleSlider, self).value()) / self._multi
459
-
460
- def setMinimum(self, value):
461
- return super(DoubleSlider, self).setMinimum(value * self._multi)
462
-
463
- def setMaximum(self, value):
464
- return super(DoubleSlider, self).setMaximum(value * self._multi)
465
-
466
- def setSingleStep(self, value):
467
- return super(DoubleSlider, self).setSingleStep(value * self._multi)
468
-
469
- def singleStep(self):
470
- return float(super(DoubleSlider, self).singleStep()) / self._multi
471
-
472
- def setValue(self, value):
473
- super(DoubleSlider, self).setValue(int(value * self._multi))
474
-
475
- def sliderChange(self, change):
476
- if change == QAbstractSlider.SliderValueChange:
477
- sr = self.style().subControlRect(QStyle.CC_Slider, self.opt, QStyle.SC_SliderHandle)
478
- bottom_right_corner = sr.bottomLeft()
479
- QToolTip.showText(self.mapToGlobal(QPoint(bottom_right_corner.x(), bottom_right_corner.y())),
480
- str(self.value()), self)
481
-
482
-
483
- if __name__ == '__main__':
484
- from bt_data import BTData
485
-
486
- bt = BTData()
487
-
488
- app = QApplication(sys.argv)
489
- dlg = ToolWidgets('Raster Line Attributes',
490
- bt.get_bera_tool_args('Raster Line Attributes'),
491
- bt.show_advanced)
492
- dlg.show()
493
- sys.exit(app.exec_())
1
+ """
2
+ Copyright (C) 2025 Applied Geospatial Research Group.
3
+
4
+ This script is licensed under the GNU General Public License v3.0.
5
+ See <https://gnu.org/licenses/gpl-3.0> for full license details.
6
+
7
+ Author: Richard Zeng
8
+
9
+ Description:
10
+ This script is part of the BERA Tools.
11
+ Webpage: https://github.com/appliedgrg/beratools
12
+
13
+ The purpose of this script is to provide all kinds of widgets for tool parameters.
14
+ """
15
+ import json
16
+ import os
17
+ import sys
18
+ from collections import OrderedDict
19
+ from pathlib import Path
20
+
21
+ import numpy as np
22
+ import pyogrio
23
+ from PyQt5 import QtCore, QtWidgets
24
+
25
+ BT_LABEL_MIN_WIDTH = 130
26
+
27
+ class ToolWidgets(QtWidgets.QWidget):
28
+ """ToolWidgets class for creating widgets for tool parameters."""
29
+
30
+ signal_save_tool_params = QtCore.pyqtSignal(object)
31
+
32
+ def __init__(self, tool_name, tool_args, show_advanced, parent=None):
33
+ super(ToolWidgets, self).__init__(parent)
34
+
35
+ self.tool_name = tool_name
36
+ self.show_advanced = show_advanced
37
+ self.current_tool_api = ''
38
+ self.widget_list = []
39
+ self.setWindowTitle("Tool widgets")
40
+
41
+ self.create_widgets(tool_args)
42
+ layout = QtWidgets.QVBoxLayout()
43
+
44
+ for item in self.widget_list:
45
+ layout.addWidget(item)
46
+
47
+ self.save_button = QtWidgets.QPushButton('Save Parameters')
48
+ self.save_button.clicked.connect(self.save_tool_parameters)
49
+ self.save_button.setFixedSize(200, 50)
50
+ layout.addSpacing(20)
51
+ self.setLayout(layout)
52
+
53
+ def get_widgets_arguments(self):
54
+ args = {}
55
+ param_missing = False
56
+ for widget in self.widget_list:
57
+ v = widget.get_value()
58
+ if v:
59
+ args.update(v)
60
+ else:
61
+ print(f'[Missing argument]: {widget.name} not specified.', 'missing')
62
+ param_missing = True
63
+
64
+ if param_missing:
65
+ args = None
66
+
67
+ return args
68
+
69
+ def create_widgets(self, tool_args):
70
+ param_num = 0
71
+ for p in tool_args:
72
+ json_str = json.dumps(p, sort_keys=True, indent=2, separators=(',', ': '))
73
+ pt = p['parameter_type']
74
+ widget = None
75
+
76
+ if 'ExistingFile' in pt or 'NewFile' in pt or 'Directory' in pt:
77
+ widget = FileSelector(json_str, None)
78
+ param_num = param_num + 1
79
+ elif 'FileList' in pt:
80
+ widget = MultiFileSelector(json_str, None)
81
+ param_num = param_num + 1
82
+ elif 'Boolean' in pt:
83
+ widget = BooleanInput(json_str)
84
+ param_num = param_num + 1
85
+ elif 'OptionList' in pt:
86
+ widget = OptionsInput(json_str)
87
+ param_num = param_num + 1
88
+ elif ('Float' in pt or 'Integer' in pt or
89
+ 'Text' in pt or 'String' in pt or 'StringOrNumber' in pt or
90
+ 'StringList' in pt or 'VectorAttributeField' in pt):
91
+ widget = DataInput(json_str)
92
+ param_num = param_num + 1
93
+ else:
94
+ msg_box = QtWidgets.QMessageBox()
95
+ msg_box.setIcon(QtWidgets.QMessageBox.Warning)
96
+ msg_box.setText("Unsupported parameter type: {}.".format(pt))
97
+ msg_box.exec()
98
+
99
+ param_value = None
100
+ if 'saved_value' in p.keys():
101
+ param_value = p['saved_value']
102
+ if param_value is None:
103
+ param_value = p['default_value']
104
+ if param_value is not None:
105
+ if type(widget) is OptionsInput:
106
+ widget.value = param_value
107
+ elif widget:
108
+ widget.value = param_value
109
+ else:
110
+ print('No default value found: {}'.format(p['name']))
111
+
112
+ # hide optional widgets
113
+ if widget:
114
+ if widget.optional and widget.label:
115
+ widget.label.setStyleSheet(
116
+ "QtWidgets.QLabel { background-color : transparent; color : blue; }"
117
+ )
118
+
119
+ if not self.show_advanced and widget.optional:
120
+ widget.hide()
121
+
122
+ self.widget_list.append(widget)
123
+
124
+ def update_widgets(self, values_dict):
125
+ for key, value in values_dict.items():
126
+ for item in self.widget_list:
127
+ if key == item.flag:
128
+ item.set_value(value)
129
+
130
+ def save_tool_parameters(self):
131
+ params = {}
132
+ for item in self.widget_list:
133
+ if item.flag:
134
+ params[item.flag] = item.get_value()
135
+
136
+ self.signal_save_tool_params.emit(params)
137
+
138
+ def load_default_args(self):
139
+ for item in self.widget_list:
140
+ item.set_default_value()
141
+
142
+
143
+ def get_layers(gpkg_file):
144
+ try:
145
+ # Get the list of layers and their geometry types from the GeoPackage file
146
+ layers_info = pyogrio.list_layers(gpkg_file)
147
+
148
+ # Check if layers_info is in the expected format
149
+ if isinstance(layers_info, np.ndarray) and all(
150
+ isinstance(layer, np.ndarray) and len(layer) >= 2 for layer in layers_info
151
+ ):
152
+ # Create a dictionary where the key is the layer name
153
+ # and the value is the geometry type
154
+ layers_dict = OrderedDict((layer[0], layer[1]) for layer in layers_info)
155
+ return layers_dict
156
+ else:
157
+ # If the format is not correct, raise an exception with a detailed message
158
+ raise ValueError(
159
+ "Expected a list of lists or tuples with layer name and geometry type."
160
+ )
161
+
162
+ except Exception as e:
163
+ print(f"Error retrieving layers from GeoPackage '{gpkg_file}': {e}")
164
+ raise
165
+
166
+
167
+ class FileSelector(QtWidgets.QWidget):
168
+ """FileSelector class for creating file selection widgets."""
169
+
170
+ def __init__(self, json_str, parent=None):
171
+ super(FileSelector, self).__init__(parent)
172
+
173
+ # Parsing the JSON data
174
+ params = json.loads(json_str)
175
+ self.name = params['name']
176
+ self.description = params['description']
177
+ self.flag = params['flag']
178
+ self.layer_flag = None
179
+ self.saved_layer = ''
180
+ if 'layer' in params.keys():
181
+ self.layer_flag = params['layer']['layer_name']
182
+ self.saved_layer = params['layer']['layer_value']
183
+
184
+ self.gpkg_layers = None
185
+ self.output = params['output'] # Ensure output flag is read
186
+ self.parameter_type = params['parameter_type']
187
+ self.file_type = ""
188
+ if "ExistingFile" in self.parameter_type:
189
+ self.file_type = params['parameter_type']['ExistingFile']
190
+ elif "NewFile" in self.parameter_type:
191
+ self.file_type = params['parameter_type']['NewFile']
192
+ self.optional = params['optional']
193
+
194
+ self.default_value = params['default_value']
195
+ self.value = self.default_value
196
+ self.selected_layer = None # Add attribute for selected layer
197
+ if 'saved_value' in params.keys():
198
+ self.value = params['saved_value']
199
+
200
+ self.layout = QtWidgets.QHBoxLayout()
201
+ self.label = QtWidgets.QLabel(self.name)
202
+ self.label.setMinimumWidth(200)
203
+ self.in_file = QtWidgets.QLineEdit(self.value)
204
+ self.btn_select = QtWidgets.QPushButton("...")
205
+ self.btn_select.clicked.connect(self.select_file)
206
+
207
+ # ComboBox for displaying GeoPackage layers
208
+ self.layer_combo = QtWidgets.QComboBox()
209
+ self.layer_combo.setVisible(False) # Initially hidden
210
+ # Connect layer change event
211
+ self.layer_combo.currentTextChanged.connect(self.set_layer)
212
+ self.layout.addWidget(self.label)
213
+ self.layout.addWidget(self.in_file)
214
+ self.layout.addWidget(self.layer_combo)
215
+ self.layout.addWidget(self.btn_select)
216
+
217
+ self.setLayout(self.layout)
218
+
219
+ # text changed
220
+ self.in_file.textChanged.connect(self.file_name_edited)
221
+
222
+ # Handle showing the layer combo and making it editable when needed
223
+ if self.value.lower().endswith('.gpkg'):
224
+ self.layer_combo.setVisible(True) # Show the combo box if it's a .gpkg
225
+ if self.output:
226
+ self.layer_combo.setEditable(True) # Ensure editable if output is True
227
+ self.layer_combo.addItem("") # Add an empty item to the combo box
228
+ # If the .gpkg file doesn't exist, show empty layer
229
+ if not os.path.exists(self.value):
230
+ self.layer_combo.clear() # Clear the combo box
231
+ self.layer_combo.addItem("layer_name") # Show "layer_name"
232
+ else:
233
+ self.layer_combo.setEditable(False) # Non-editable if output is False
234
+ self.load_gpkg_layers(self.value) # Load layers if output is False
235
+
236
+ # check saved layer existence, if True then set it to selected
237
+ index = self.search_saved_combo_items()
238
+ if index != -1:
239
+ self.layer_combo.setCurrentIndex(index)
240
+
241
+ # If the file is not a .gpkg, don't show the combo box at all
242
+ elif self.layer_combo.isVisible():
243
+ self.layer_combo.setVisible(False)
244
+
245
+ self.update_combo_visibility() # Update combo visibility after init
246
+
247
+ def update_combo_visibility(self):
248
+ if self.value.lower().endswith('.gpkg'):
249
+ self.layer_combo.setVisible(True)
250
+ if os.path.exists(self.value):
251
+ if self.output:
252
+ self.layer_combo.setEditable(True)
253
+ if self.layer_combo.count() == 0:
254
+ self.layer_combo.addItem("layer_name")
255
+ self.load_gpkg_layers(self.value)
256
+ elif self.layer_combo.itemText(0) != "layer_name":
257
+ self.layer_combo.insertItem(0, "layer_name")
258
+ self.load_gpkg_layers(self.value)
259
+ else: # output is False
260
+ self.layer_combo.setEditable(False)
261
+ if (
262
+ self.layer_combo.count() == 0
263
+ or self.layer_combo.itemText(0) == "layer_name"
264
+ ):
265
+ self.layer_combo.clear()
266
+ self.load_gpkg_layers(self.value)
267
+ else: # gpkg does not exist
268
+ self.layer_combo.clear()
269
+ if self.output:
270
+ self.layer_combo.setEditable(True)
271
+ self.layer_combo.addItem("layer_name")
272
+ else:
273
+ self.layer_combo.addItem("layer_name")
274
+
275
+ self.layer_combo.adjustSize()
276
+ else:
277
+ self.layer_combo.setVisible(False)
278
+
279
+ self.adjustSize()
280
+ if self.parentWidget():
281
+ self.parentWidget().layout().invalidate()
282
+ self.parentWidget().adjustSize()
283
+ self.parentWidget().update()
284
+
285
+ def select_file(self):
286
+ try:
287
+ dialog = QtWidgets.QFileDialog(self)
288
+ dialog.setViewMode(QtWidgets.QFileDialog.Detail)
289
+ dialog.setDirectory(str(Path(self.value).parent))
290
+ dialog.selectFile(Path(self.value).name)
291
+ file_names = None
292
+
293
+ file_types = "All files (*.*)"
294
+
295
+ if 'RasterAndVector' in self.file_type:
296
+ file_types = """Shapefiles (*.shp);;
297
+ Raster files (*.dep *.tif *.tiff *.bil *.flt *.sdat *.asc *grd)"""
298
+ elif 'Raster' in self.file_type:
299
+ file_types = """Tiff raster files (*.tif *.tiff);;
300
+ Other raster files (*.dep *.bil *.flt *.sdat *.asc *grd)"""
301
+ elif 'Lidar' in self.file_type:
302
+ file_types = "LiDAR files (*.las *.zlidar *.laz *.zip)"
303
+ elif 'Vector' in self.file_type:
304
+ file_types = """GeoPackage (*.gpkg);;
305
+ Shapefiles (*.shp)"""
306
+ elif 'Text' in self.file_type:
307
+ file_types = "Text files (*.txt);; all files (*.*)"
308
+ elif 'Csv' in self.file_type:
309
+ file_types = "CSV files (*.csv);; all files (*.*)"
310
+ elif 'Dat' in self.file_type:
311
+ file_types = "Binary data files (*.dat);; all files (*.*)"
312
+ elif 'Html' in self.file_type:
313
+ file_types = "HTML files (*.html)"
314
+ elif 'json' in self.file_type or 'JSON' in self.file_type:
315
+ file_types = "JSON files (*.json)"
316
+
317
+ # Check for GeoPackage/Shapefile first in filter order by current value
318
+ if self.value.lower().endswith('.gpkg'):
319
+ file_types = """GeoPackage (*.gpkg);;
320
+ Shapefiles (*.shp);;
321
+ All files (*.*)"""
322
+ elif self.value.lower().endswith('.shp'):
323
+ file_types = """Shapefiles (*.shp);;
324
+ GeoPackage (*.gpkg);;
325
+ All files (*.*)"""
326
+
327
+ dialog.setNameFilter(file_types)
328
+
329
+ if "ExistingFile" in self.parameter_type:
330
+ dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
331
+ else:
332
+ dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
333
+
334
+ if dialog.exec_():
335
+ file_names = dialog.selectedFiles()
336
+
337
+ if not file_names:
338
+ return
339
+
340
+ result = file_names[0]
341
+ base_name, selected_ext = os.path.splitext(result)
342
+ selected_filter = dialog.selectedNameFilter()
343
+
344
+ if selected_filter:
345
+ filter_parts = selected_filter.split("(*")
346
+ if len(filter_parts) > 1:
347
+ extensions_str = filter_parts[1].replace(")", "")
348
+ extensions = extensions_str.split(" ")
349
+
350
+ if extensions:
351
+ preferred_ext = extensions[0].strip()
352
+ if not preferred_ext.startswith("."):
353
+ preferred_ext = "." + preferred_ext
354
+ if not selected_ext:
355
+ result = f"{base_name}{preferred_ext}"
356
+ elif not selected_ext: # No filter and no extension
357
+ result = f"{base_name}.txt"
358
+
359
+ self.set_value(result)
360
+
361
+ if result.lower().endswith('.gpkg'):
362
+ if not os.path.exists(result):
363
+ self.layer_combo.clear()
364
+ self.layer_combo.addItem("layer_name")
365
+ else:
366
+ self.load_gpkg_layers(result)
367
+ if self.output:
368
+ self.layer_combo.setEditable(True)
369
+ else:
370
+ self.layer_combo.setVisible(False)
371
+
372
+ # Update combo visibility after file selection
373
+ self.update_combo_visibility()
374
+ except Exception as e:
375
+ print(e)
376
+ msg_box = QtWidgets.QMessageBox()
377
+ msg_box.setIcon(QtWidgets.QMessageBox.Warning)
378
+ msg_box.setText("Could not find the selected file.")
379
+ msg_box.exec()
380
+
381
+ def load_gpkg_layers(self, gpkg_file):
382
+ """Load layers from a GeoPackage and populate the combo box using get_layers."""
383
+ try:
384
+ # Print the file path to verify it's correct
385
+ # print(f"Attempting to load layers from: {gpkg_file}")
386
+
387
+ # Use get_layers to load layers from the GeoPackage
388
+ self.gpkg_layers = get_layers(gpkg_file)
389
+
390
+ # Check if layers is empty
391
+ if not self.gpkg_layers:
392
+ raise ValueError("No layers found in the GeoPackage.")
393
+
394
+ # Clear any existing layers in the combo box
395
+ self.layer_combo.clear()
396
+
397
+ # Iterate over the layers dictionary
398
+ # and add each layer name with geometry type to the combo box
399
+ for layer_name, geometry_type in self.gpkg_layers.items():
400
+ self.layer_combo.addItem(f"{layer_name} ({geometry_type})")
401
+
402
+ # Set the tooltip for the layer list widget
403
+ self.layer_combo.setToolTip("Select layer")
404
+
405
+ # Make the combo box visible
406
+ self.layer_combo.setVisible(True)
407
+
408
+ except Exception as e:
409
+ # Print the full error message for debugging purposes
410
+ print(f"Error loading GeoPackage layers: {e}")
411
+
412
+ # Show a message box with the error
413
+ msg_box = QtWidgets.QMessageBox()
414
+ msg_box.setIcon(QtWidgets.QMessageBox.Warning)
415
+ msg_box.setText(f"Could not load layers from GeoPackage: {gpkg_file}")
416
+ msg_box.setDetailedText(str(e)) # Show detailed error message
417
+ msg_box.exec()
418
+
419
+ def file_name_edited(self):
420
+ # Step 1: Get the current value in the file input field
421
+ new_value = self.in_file.text()
422
+ self.value = new_value # update file name
423
+
424
+ # Step 2: Check if the new value ends with a .gpkg extension
425
+ if new_value.lower().endswith('.gpkg'):
426
+ # If it's a GeoPackage, check if the file exists
427
+ if os.path.exists(new_value):
428
+ # File exists, load layers from the GeoPackage
429
+ self.load_gpkg_layers(new_value)
430
+ self.layer_combo.setVisible(True) # Show the layer combo box
431
+ self.update_combo_visibility() # Ensure layers are updated properly
432
+ else:
433
+ # File doesn't exist, clear the layer combo box and show message
434
+ self.layer_combo.clear()
435
+ self.layer_combo.addItem("layer_name")
436
+ if self.output:
437
+ self.layer_combo.setEditable(True)
438
+
439
+ # Show the layer combo box but indicate no layers
440
+ self.layer_combo.setVisible(True)
441
+ else:
442
+ # If it's not a GeoPackage, hide the layer combo box
443
+ self.layer_combo.setVisible(False)
444
+
445
+ # Optional: Adjust the combo box visibility and layout
446
+ self.adjustSize()
447
+ if self.parentWidget():
448
+ self.parentWidget().layout().invalidate()
449
+ self.parentWidget().adjustSize()
450
+ self.parentWidget().update()
451
+
452
+ def set_value(self, value):
453
+ # Check if the value has an extension
454
+ base_name, ext = os.path.splitext(value)
455
+
456
+ # Only append an extension if none exists AND the value doesn't end with a dot
457
+ if not ext: # If there's no extension
458
+ if not value.endswith("."): # If the user hasn't typed a dot at the end
459
+ # Don't force the .txt extension unless the filename doesn't have one
460
+ # Add default extension for other cases
461
+ if not value.endswith(".gpkg") and not value.endswith(".shp"):
462
+ value = f"{base_name}.txt"
463
+ # If the value ends with a dot (like `file.`), don't append anything yet
464
+
465
+ # If the value ends with a dot, don't append an extension.
466
+ elif value.endswith("."):
467
+ value = base_name # Strip the dot
468
+
469
+ self.value = value
470
+ self.in_file.setText(self.value)
471
+ self.in_file.setToolTip(self.value)
472
+ self.update_combo_visibility()
473
+
474
+ def set_layer(self, layer):
475
+ # Store only the selected layer's name (key) from the combo box display
476
+ # The layer is in the format: "layer_name (geometry_type)"
477
+ # Get only the layer name (before the space)
478
+ self.selected_layer = layer.split(" ")[0]
479
+ # print(f"Selected Layer: {self.selected_layer}")
480
+
481
+ def get_value(self):
482
+ # Return both the file path and the selected layer
483
+ value = {self.flag: self.value}
484
+ if self.layer_flag and self.selected_layer:
485
+ # Store the layer name (key)
486
+ value.update({self.layer_flag: self.selected_layer})
487
+
488
+ return value
489
+
490
+ def search_saved_combo_items(self):
491
+ """
492
+ Search saved layer in combo box items.
493
+
494
+ Returns:
495
+ If found, then return the index, or return -1
496
+
497
+ """
498
+ if not self.gpkg_layers:
499
+ return -1
500
+
501
+ for idx, key in enumerate(self.gpkg_layers.keys()):
502
+ if key == self.saved_layer:
503
+ return idx
504
+
505
+ return -1
506
+
507
+ # TODO: check if this class is needed
508
+ class MultiFileSelector(QtWidgets.QWidget):
509
+ """MultiFileSelector class for creating multiple file selection widgets."""
510
+
511
+ def __init__(self, json_str, parent=None):
512
+ super(MultiFileSelector, self).__init__(parent)
513
+ pass
514
+
515
+ # TODO: check if this class is needed
516
+ class BooleanInput(QtWidgets.QWidget):
517
+ """BooleanInput class for creating boolean input widgets."""
518
+
519
+ def __init__(self, json_str, parent=None):
520
+ super(BooleanInput, self).__init__(parent)
521
+ pass
522
+
523
+
524
+ class OptionsInput(QtWidgets.QWidget):
525
+ """OptionsInput class for creating option selection widgets."""
526
+
527
+ def __init__(self, json_str, parent=None):
528
+ super(OptionsInput, self).__init__(parent)
529
+
530
+ # first make sure that the json data has the correct fields
531
+ params = json.loads(json_str)
532
+ self.name = params['name']
533
+ self.description = params['description']
534
+ self.flag = params['flag']
535
+ self.parameter_type = params['parameter_type']
536
+ self.optional = params['optional']
537
+ self.data_type = params['data_type']
538
+
539
+ self.default_value = str(params['default_value'])
540
+ self.value = self.default_value
541
+ if 'saved_value' in params.keys():
542
+ self.value = params['saved_value']
543
+
544
+ self.label = QtWidgets.QLabel(self.name)
545
+ self.label.setMinimumWidth(BT_LABEL_MIN_WIDTH)
546
+ self.combobox = QtWidgets.QComboBox()
547
+ self.combobox.currentIndexChanged.connect(self.selection_change)
548
+
549
+ i = 1
550
+ default_index = -1
551
+ self.option_list = params['parameter_type']['OptionList']
552
+ if self.option_list:
553
+ # convert to strings
554
+ self.option_list = [str(item) for item in self.option_list]
555
+ values = ()
556
+ for v in self.option_list:
557
+ values += (v,)
558
+ if v == str(self.value):
559
+ default_index = i - 1
560
+ i = i + 1
561
+
562
+ self.combobox.addItems(self.option_list)
563
+ self.combobox.setCurrentIndex(default_index)
564
+
565
+ self.layout = QtWidgets.QHBoxLayout()
566
+ self.layout.addWidget(self.label)
567
+ self.layout.addWidget(self.combobox)
568
+ self.setLayout(self.layout)
569
+
570
+ def selection_change(self, i):
571
+ self.value = self.option_list[i]
572
+
573
+ def set_value(self, value):
574
+ self.value = value
575
+ for v in self.option_list:
576
+ if value == v:
577
+ self.combobox.setCurrentIndex(self.option_list.index(v))
578
+
579
+ def set_default_value(self):
580
+ self.value = self.default_value
581
+ for v in self.option_list:
582
+ if self.value == v:
583
+ self.combobox.setCurrentIndex(self.option_list.index(v))
584
+
585
+ def get_value(self):
586
+ return {self.flag: self.value}
587
+
588
+
589
+ class DataInput(QtWidgets.QWidget):
590
+ """DataInput class for creating data input widgets."""
591
+
592
+ def __init__(self, json_str, parent=None):
593
+ super(DataInput, self).__init__(parent)
594
+
595
+ # first make sure that the json data has the correct fields
596
+ params = json.loads(json_str)
597
+ self.name = params['name']
598
+ self.description = params['description']
599
+ self.flag = params['flag']
600
+ self.parameter_type = params['parameter_type']
601
+ self.optional = params['optional']
602
+
603
+ self.default_value = params['default_value']
604
+ self.value = self.default_value
605
+ if 'saved_value' in params.keys():
606
+ self.value = params['saved_value']
607
+
608
+ self.label = QtWidgets.QLabel(self.name)
609
+ self.label.setMinimumWidth(BT_LABEL_MIN_WIDTH)
610
+ self.data_input = None
611
+
612
+ if "Integer" in self.parameter_type:
613
+ self.data_input = QtWidgets.QSpinBox()
614
+ elif "Float" in self.parameter_type or "Double" in self.parameter_type:
615
+ self.data_input = QtWidgets.QDoubleSpinBox()
616
+
617
+ if self.data_input:
618
+ self.data_input.setValue(self.value)
619
+
620
+ self.data_input.valueChanged.connect(self.update_value)
621
+
622
+ self.layout = QtWidgets.QHBoxLayout()
623
+ self.layout.addWidget(self.label)
624
+ self.layout.addWidget(self.data_input)
625
+ self.setLayout(self.layout)
626
+
627
+ def update_value(self):
628
+ self.value = self.data_input.value()
629
+
630
+ def get_value(self):
631
+ v = self.value
632
+ if v is not None:
633
+ if "Integer" in self.parameter_type:
634
+ value = int(self.value)
635
+ elif "Float" in self.parameter_type:
636
+ value = float(self.value)
637
+ elif "Double" in self.parameter_type:
638
+ value = float(self.value)
639
+ else: # String or StringOrNumber types
640
+ value = self.value
641
+
642
+ return {self.flag: value}
643
+ else:
644
+ if not self.optional:
645
+ msg_box = QtWidgets.QMessageBox()
646
+ msg_box.setIcon(QtWidgets.QMessageBox.Warning)
647
+ msg_box.setText("Unknown non-optional parameter {}.".format(self.flag))
648
+ msg_box.exec()
649
+
650
+ return None
651
+
652
+ def set_value(self, value):
653
+ if self.data_input:
654
+ self.data_input.setValue(value)
655
+ self.update_value()
656
+
657
+ def set_default_value(self):
658
+ if self.data_input:
659
+ self.data_input.setValue(self.default_value)
660
+ self.update_value()
661
+
662
+
663
+ class DoubleSlider(QtWidgets.QSlider):
664
+ """DoubleSlider class for creating double slider widgets."""
665
+
666
+ # create our signal that we can connect to if necessary
667
+ doubleValueChanged = QtCore.pyqtSignal(float)
668
+
669
+ def __init__(self, decimals=3, *args, **kargs):
670
+ super(DoubleSlider, self).__init__(QtCore.Qt.Horizontal)
671
+ self._multi = 10 ** decimals
672
+
673
+ self.opt = QtWidgets.QStyleOptionSlider()
674
+ self.initStyleOption(self.opt)
675
+
676
+ self.valueChanged.connect(self.emit_double_value_changed)
677
+
678
+ def emit_double_value_changed(self):
679
+ value = float(super(DoubleSlider, self).value()) / self._multi
680
+ self.doubleValueChanged.emit(value)
681
+
682
+ def value(self):
683
+ return float(super(DoubleSlider, self).value()) / self._multi
684
+
685
+ def setMinimum(self, value):
686
+ return super(DoubleSlider, self).setMinimum(value * self._multi)
687
+
688
+ def setMaximum(self, value):
689
+ return super(DoubleSlider, self).setMaximum(value * self._multi)
690
+
691
+ def setSingleStep(self, value):
692
+ return super(DoubleSlider, self).setSingleStep(value * self._multi)
693
+
694
+ def singleStep(self):
695
+ return float(super(DoubleSlider, self).singleStep()) / self._multi
696
+
697
+ def setValue(self, value):
698
+ super(DoubleSlider, self).setValue(int(value * self._multi))
699
+
700
+ def sliderChange(self, change):
701
+ if change == QtWidgets.QAbstractSlider.SliderValueChange:
702
+ sr = self.style().subControlRect(
703
+ QtWidgets.QStyle.CC_Slider, self.opt, QtWidgets.QStyle.SC_SliderHandle
704
+ )
705
+ bottom_right_corner = sr.bottomLeft()
706
+ QtWidgets.QToolTip.showText(
707
+ self.mapToGlobal(
708
+ QtCore.QPoint(bottom_right_corner.x(), bottom_right_corner.y())
709
+ ),
710
+ str(self.value()),
711
+ self,
712
+ )
713
+
714
+
715
+ if __name__ == '__main__':
716
+ from bt_data import BTData
717
+
718
+ bt = BTData()
719
+
720
+ app = QtWidgets.QApplication(sys.argv)
721
+ dlg = ToolWidgets('Raster Line Attributes',
722
+ bt.get_bera_tool_args('Raster Line Attributes'),
723
+ bt.show_advanced)
724
+ dlg.show()
725
+ sys.exit(app.exec_())