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.
- beratools/__init__.py +1 -7
- beratools/core/algo_centerline.py +491 -351
- beratools/core/algo_common.py +497 -0
- beratools/core/algo_cost.py +192 -0
- beratools/core/{dijkstra_algorithm.py → algo_dijkstra.py} +503 -460
- beratools/core/algo_footprint_rel.py +577 -0
- beratools/core/algo_line_grouping.py +944 -0
- beratools/core/algo_merge_lines.py +214 -0
- beratools/core/algo_split_with_lines.py +304 -0
- beratools/core/algo_tiler.py +428 -0
- beratools/core/algo_vertex_optimization.py +469 -0
- beratools/core/constants.py +52 -86
- beratools/core/logger.py +76 -85
- beratools/core/tool_base.py +196 -133
- beratools/gui/__init__.py +11 -15
- beratools/gui/{beratools.json → assets/beratools.json} +2185 -2300
- beratools/gui/batch_processing_dlg.py +513 -463
- beratools/gui/bt_data.py +481 -487
- beratools/gui/bt_gui_main.py +710 -691
- beratools/gui/main.py +26 -0
- beratools/gui/map_window.py +162 -146
- beratools/gui/tool_widgets.py +725 -493
- beratools/tools/Beratools_r_script.r +1120 -1120
- beratools/tools/Ht_metrics.py +116 -116
- beratools/tools/__init__.py +7 -7
- beratools/tools/batch_processing.py +136 -132
- beratools/tools/canopy_threshold_relative.py +672 -670
- beratools/tools/canopycostraster.py +222 -222
- beratools/tools/centerline.py +136 -176
- beratools/tools/common.py +857 -885
- beratools/tools/fl_regen_csf.py +428 -428
- beratools/tools/forest_line_attributes.py +408 -408
- beratools/tools/line_footprint_absolute.py +213 -363
- beratools/tools/line_footprint_fixed.py +436 -282
- beratools/tools/line_footprint_functions.py +733 -720
- beratools/tools/line_footprint_relative.py +73 -64
- beratools/tools/line_grouping.py +45 -0
- beratools/tools/ln_relative_metrics.py +615 -615
- beratools/tools/r_cal_lpi_elai.r +24 -24
- beratools/tools/r_generate_pd_focalraster.r +100 -100
- beratools/tools/r_interface.py +79 -79
- beratools/tools/r_point_density.r +8 -8
- beratools/tools/rpy_chm2trees.py +86 -86
- beratools/tools/rpy_dsm_chm_by.py +81 -81
- beratools/tools/rpy_dtm_by.py +63 -63
- beratools/tools/rpy_find_cellsize.py +43 -43
- beratools/tools/rpy_gnd_csf.py +74 -74
- beratools/tools/rpy_hummock_hollow.py +85 -85
- beratools/tools/rpy_hummock_hollow_raster.py +71 -71
- beratools/tools/rpy_las_info.py +51 -51
- beratools/tools/rpy_laz2las.py +40 -40
- beratools/tools/rpy_lpi_elai_lascat.py +466 -466
- beratools/tools/rpy_normalized_lidar_by.py +56 -56
- beratools/tools/rpy_percent_above_dbh.py +80 -80
- beratools/tools/rpy_points2trees.py +88 -88
- beratools/tools/rpy_vegcoverage.py +94 -94
- beratools/tools/tiler.py +48 -206
- beratools/tools/tool_template.py +69 -54
- beratools/tools/vertex_optimization.py +61 -620
- beratools/tools/zonal_threshold.py +144 -144
- beratools-0.2.2.dist-info/METADATA +108 -0
- beratools-0.2.2.dist-info/RECORD +74 -0
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/WHEEL +1 -1
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/licenses/LICENSE +22 -22
- beratools/gui/cli.py +0 -18
- beratools/gui/gui.json +0 -8
- beratools/gui_tk/ASCII Banners.txt +0 -248
- beratools/gui_tk/__init__.py +0 -20
- beratools/gui_tk/beratools_main.py +0 -515
- beratools/gui_tk/bt_widgets.py +0 -442
- beratools/gui_tk/cli.py +0 -18
- beratools/gui_tk/img/BERALogo.png +0 -0
- beratools/gui_tk/img/closed.gif +0 -0
- beratools/gui_tk/img/closed.png +0 -0
- beratools/gui_tk/img/open.gif +0 -0
- beratools/gui_tk/img/open.png +0 -0
- beratools/gui_tk/img/tool.gif +0 -0
- beratools/gui_tk/img/tool.png +0 -0
- beratools/gui_tk/main.py +0 -14
- beratools/gui_tk/map_window.py +0 -144
- beratools/gui_tk/runner.py +0 -1481
- beratools/gui_tk/tooltip.py +0 -55
- beratools/third_party/pyqtlet2/__init__.py +0 -9
- beratools/third_party/pyqtlet2/leaflet/__init__.py +0 -26
- beratools/third_party/pyqtlet2/leaflet/control/__init__.py +0 -6
- beratools/third_party/pyqtlet2/leaflet/control/control.py +0 -59
- beratools/third_party/pyqtlet2/leaflet/control/draw.py +0 -52
- beratools/third_party/pyqtlet2/leaflet/control/layers.py +0 -20
- beratools/third_party/pyqtlet2/leaflet/core/Parser.py +0 -24
- beratools/third_party/pyqtlet2/leaflet/core/__init__.py +0 -2
- beratools/third_party/pyqtlet2/leaflet/core/evented.py +0 -180
- beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +0 -34
- beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +0 -30
- beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/layer.py +0 -105
- beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +0 -45
- beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +0 -91
- beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +0 -2
- beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +0 -4
- beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +0 -16
- beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +0 -15
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +0 -14
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +0 -14
- beratools/third_party/pyqtlet2/leaflet/map/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/map/map.py +0 -220
- beratools/third_party/pyqtlet2/mapwidget.py +0 -45
- beratools/third_party/pyqtlet2/web/custom.js +0 -43
- beratools/third_party/pyqtlet2/web/map.html +0 -23
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +0 -656
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +0 -6
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +0 -14
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +0 -4
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +0 -22
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +0 -43
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +0 -20
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +0 -156
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +0 -10
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +0 -10
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +0 -22
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +0 -57
- beratools/tools/forest_line_ecosite.py +0 -216
- beratools/tools/lapis_all.py +0 -103
- beratools/tools/least_cost_path_from_chm.py +0 -152
- beratools-0.2.0.dist-info/METADATA +0 -63
- beratools-0.2.0.dist-info/RECORD +0 -142
- /beratools/gui/{img → assets}/BERALogo.png +0 -0
- /beratools/gui/{img → assets}/closed.gif +0 -0
- /beratools/gui/{img → assets}/closed.png +0 -0
- /beratools/{gui_tk → gui/assets}/gui.json +0 -0
- /beratools/gui/{img → assets}/open.gif +0 -0
- /beratools/gui/{img → assets}/open.png +0 -0
- /beratools/gui/{img → assets}/tool.gif +0 -0
- /beratools/gui/{img → assets}/tool.png +0 -0
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/entry_points.txt +0 -0
beratools/gui/tool_widgets.py
CHANGED
|
@@ -1,493 +1,725 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from PyQt5
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
59
|
-
args
|
|
60
|
-
else:
|
|
61
|
-
print(f'[Missing argument]: {widget.name}
|
|
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 '
|
|
77
|
-
widget =
|
|
78
|
-
param_num = param_num + 1
|
|
79
|
-
elif '
|
|
80
|
-
widget =
|
|
81
|
-
param_num = param_num + 1
|
|
82
|
-
elif '
|
|
83
|
-
widget =
|
|
84
|
-
param_num = param_num + 1
|
|
85
|
-
elif '
|
|
86
|
-
widget =
|
|
87
|
-
param_num = param_num + 1
|
|
88
|
-
elif '
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
msg_box
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
param_value
|
|
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
|
-
self.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
def
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
self.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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_())
|