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
beratools/gui/bt_data.py CHANGED
@@ -1,487 +1,481 @@
1
- #!/usr/bin/env python3
2
- """ This file is intended to be a helper for running BERA tools from a Python script.
3
- """
4
-
5
- # This script is part of the BERA Tools geospatial library.
6
- # Created: 23/03/2023
7
- # Author: Richard Zeng
8
- # License: MIT
9
-
10
- import os
11
- from os import path
12
- from pathlib import Path
13
- from inspect import getsourcefile
14
-
15
- import platform
16
- import json
17
- from json.decoder import JSONDecodeError
18
- import multiprocessing
19
- from collections import OrderedDict
20
-
21
- from beratools.tools.common import *
22
- from beratools.core.constants import *
23
-
24
- running_windows = platform.system() == 'Windows'
25
-
26
-
27
- def default_callback(value):
28
- """
29
- A simple default callback that outputs using the print function. When
30
- tools are called without providing a custom callback, this function
31
- will be used to print to standard output.
32
- """
33
- print(value)
34
-
35
-
36
- class BTData(object):
37
- """
38
- An object for interfacing with the BERA Tools executable.
39
- """
40
-
41
- def __init__(self):
42
- if running_windows:
43
- self.ext = '.exe'
44
- else:
45
- self.ext = ''
46
- self.exe_name = "BERA_tools{}".format(self.ext)
47
- self.exe_path = path.dirname(path.abspath(__file__))
48
-
49
- self.work_dir = ""
50
- self.user_folder = Path('')
51
- self.data_folder = Path('')
52
- self.verbose = False
53
- self.show_advanced = BT_SHOW_ADVANCED_OPTIONS
54
- self.max_procs = -1
55
- self.recent_tool = None
56
- self.ascii_art = None
57
- self.get_working_dir()
58
- self.get_user_folder()
59
-
60
- # set maximum available cpu core for tools
61
- self.max_cpu_cores = min(BT_MAXIMUM_CPU_CORES, multiprocessing.cpu_count())
62
-
63
- # load bera tools
64
- self.tool_history = []
65
- self.settings = {}
66
- self.bera_tools = None
67
- self.tools_list = []
68
- self.sorted_tools = []
69
- self.upper_toolboxes = []
70
- self.lower_toolboxes = []
71
- self.toolbox_list = []
72
- self.get_bera_tools()
73
- self.get_bera_tool_list()
74
- self.get_bera_toolboxes()
75
- self.sort_toolboxes()
76
-
77
- self.setting_file = None
78
- self.get_data_folder()
79
- self.get_setting_file()
80
- self.gui_setting_file = Path(self.exe_path).joinpath(r'gui.json')
81
-
82
- self.load_saved_tool_info()
83
- self.load_gui_data()
84
- self.get_tool_history()
85
-
86
- self.default_callback = default_callback
87
- self.start_minimized = False
88
-
89
- def set_bera_dir(self, path_str):
90
- """
91
- Sets the directory to the BERA Tools executable file.
92
- """
93
- self.exe_path = path_str
94
-
95
- def add_tool_history(self, tool, params):
96
- if 'tool_history' not in self.settings:
97
- self.settings['tool_history'] = OrderedDict()
98
-
99
- self.settings['tool_history'][tool] = params
100
- self.settings['tool_history'].move_to_end(tool, last=False)
101
-
102
- def save_tool_info(self):
103
- if self.recent_tool:
104
- if 'gui_parameters' not in self.settings.keys():
105
- self.settings['gui_parameters'] = {}
106
-
107
- self.settings['gui_parameters']['recent_tool'] = self.recent_tool
108
-
109
- with open(self.setting_file, 'w') as file_setting:
110
- try:
111
- json.dump(self.settings, file_setting, indent=4)
112
- except JSONDecodeError:
113
- pass
114
-
115
- def save_setting(self, key, value):
116
- # check setting directory existence
117
- data_path = Path(self.setting_file).resolve().parent
118
- if not data_path.exists():
119
- data_path.mkdir()
120
-
121
- self.load_saved_tool_info()
122
-
123
- if value is not None:
124
- if 'gui_parameters' not in self.settings.keys():
125
- self.settings['gui_parameters'] = {}
126
-
127
- self.settings['gui_parameters'][key] = value
128
-
129
- with open(self.setting_file, 'w') as write_settings_file:
130
- json.dump(self.settings, write_settings_file, indent=4)
131
-
132
- def get_working_dir(self):
133
- current_file = Path(getsourcefile(lambda: 0)).resolve()
134
- btool_dir = current_file.parents[1]
135
- self.work_dir = btool_dir
136
-
137
- def get_user_folder(self):
138
- self.user_folder = Path.home().joinpath('.beratools')
139
- if not self.user_folder.exists():
140
- self.user_folder.mkdir()
141
-
142
- def get_data_folder(self):
143
- self.data_folder = self.user_folder.joinpath('.data')
144
- if not self.data_folder.exists():
145
- self.data_folder.mkdir()
146
-
147
- def get_logger_file_name(self, name):
148
- if not name:
149
- name = 'beratools'
150
-
151
- logger_file_name = self.user_folder.joinpath(name).with_suffix('.log')
152
- return logger_file_name.as_posix()
153
-
154
- def get_setting_file(self):
155
- self.setting_file = self.data_folder.joinpath('saved_tool_parameters.json')
156
-
157
- def get_verbose_mode(self):
158
- return self.verbose
159
-
160
- def set_verbose_mode(self, val=True):
161
- self.verbose = val
162
- self.save_setting('verbose_mode', val)
163
-
164
- def set_max_procs(self, val=-1):
165
- """
166
- Sets the flag used by BERA Tools to determine whether to use compression for output rasters.
167
- """
168
- self.max_procs = val
169
- self.save_setting('max_procs', val)
170
-
171
- def get_max_procs(self):
172
- return self.max_procs
173
-
174
- def get_max_cpu_cores(self):
175
- return self.max_cpu_cores
176
-
177
- def run_tool(self, tool_api, args, callback=None, verbose=True):
178
- """
179
- Runs a tool and specifies tool arguments.
180
- Returns 0 if completes without error.
181
- Returns 1 if error encountered (details are sent to callback).
182
- Returns 2 if process is cancelled by user.
183
- """
184
- try:
185
- if callback is None:
186
- callback = self.default_callback
187
- except Exception as err:
188
- callback(str(err))
189
- return 1
190
-
191
- # Call script using new process to make GUI responsive
192
- try:
193
- proc = None
194
-
195
- # convert to valid json string
196
- args_string = str(args).replace("'", '"')
197
- args_string = args_string.replace('True', 'true')
198
- args_string = args_string.replace('False', 'false')
199
-
200
- tool_name = self.get_bera_tool_name(tool_api)
201
- tool_type = self.get_bera_tool_type(tool_name)
202
- tool_args = None
203
-
204
- if tool_type == 'python':
205
- tool_args = [self.work_dir.joinpath(f'tools/{tool_api}.py').as_posix(),
206
- '-i', args_string, '-p', str(self.get_max_procs()),
207
- '-v', str(self.verbose)]
208
- elif tool_type == 'executable':
209
- print(globals().get(tool_api))
210
- tool_args = globals()[tool_api](args_string)
211
- lapis_path = self.work_dir.joinpath('./third_party/Lapis_0_8')
212
- os.chdir(lapis_path.as_posix())
213
- except Exception as err:
214
- callback(str(err))
215
- return 1
216
-
217
- return tool_type, tool_args
218
-
219
- def about(self):
220
- """
221
- Retrieves the help description for BERA Tools.
222
- """
223
- try:
224
- about_text = 'BERA Tools provide a series of tools developed by AppliedGRG lab.\n\n'
225
- about_text += self.ascii_art
226
- return about_text
227
- except (OSError, ValueError) as err:
228
- return err
229
-
230
- def license(self):
231
- """
232
- Retrieves the license information for BERA Tools.
233
- """
234
- try:
235
- with open(os.path.join(self.exe_path, r'..\..\LICENSE.txt'), 'r') as f:
236
- ret = f.read()
237
-
238
- return ret
239
- except (OSError, ValueError) as err:
240
- return err
241
-
242
- def load_saved_tool_info(self):
243
- data_path = Path(self.setting_file).parent
244
- if not data_path.exists():
245
- data_path.mkdir()
246
-
247
- saved_parameters = {}
248
- json_file = Path(self.setting_file)
249
- if json_file.exists():
250
- with open(json_file) as open_file:
251
- try:
252
- saved_parameters = json.load(open_file, object_pairs_hook=OrderedDict)
253
- except json.decoder.JSONDecodeError:
254
- pass
255
-
256
- self.settings = saved_parameters
257
-
258
- # parse file
259
- if 'gui_parameters' in self.settings.keys():
260
- gui_settings = self.settings['gui_parameters']
261
-
262
- # TODO remove working dir
263
- # if 'working_directory' in gui_settings.keys():
264
- # self.work_dir = str(gui_settings['working_directory'])
265
- if 'verbose_mode' in gui_settings.keys():
266
- self.verbose = str(gui_settings['verbose_mode'])
267
- else:
268
- self.verbose = False
269
-
270
- if 'max_procs' in gui_settings.keys():
271
- self.max_procs = gui_settings['max_procs']
272
-
273
- if 'recent_tool' in gui_settings.keys():
274
- self.recent_tool = gui_settings['recent_tool']
275
- if not self.get_bera_tool_api(self.recent_tool):
276
- self.recent_tool = None
277
-
278
- def load_gui_data(self):
279
- gui_settings = {}
280
- if not self.gui_setting_file.exists():
281
- print("gui.json not exist.")
282
- else:
283
- # read the settings.json file if it exists
284
- with open(self.gui_setting_file, 'r') as file_gui:
285
- try:
286
- gui_settings = json.load(file_gui)
287
- except json.decoder.JSONDecodeError:
288
- pass
289
-
290
- # parse file
291
- if 'ascii_art' in gui_settings.keys():
292
- bera_art = ''
293
- for line_of_art in gui_settings['ascii_art']:
294
- bera_art += line_of_art
295
- self.ascii_art = bera_art
296
-
297
- def get_tool_history(self):
298
- tool_history = []
299
- self.load_saved_tool_info()
300
- if self.settings:
301
- if 'tool_history' in self.settings:
302
- tool_history = self.settings['tool_history']
303
-
304
- if tool_history:
305
- self.tool_history = []
306
- for item in tool_history:
307
- item = self.get_bera_tool_name(item)
308
- self.tool_history.append(item)
309
-
310
- def get_saved_tool_params(self, tool_api, variable=None):
311
- self.load_saved_tool_info()
312
-
313
- if 'tool_history' in self.settings:
314
- if tool_api in list(self.settings['tool_history']):
315
- tool_params = self.settings['tool_history'][tool_api]
316
- if tool_params:
317
- if variable:
318
- if variable in tool_params.keys():
319
- saved_value = tool_params[variable]
320
- return saved_value
321
- else: # return all params
322
- return tool_params
323
-
324
- return None
325
-
326
- def get_bera_tools(self):
327
- tool_json = os.path.join(self.exe_path, r'beratools.json')
328
- if os.path.exists(tool_json):
329
- tool_json = open(os.path.join(self.exe_path, r'beratools.json'))
330
- self.bera_tools = json.load(tool_json)
331
- else:
332
- print('Tool configuration file not exists')
333
-
334
- def get_bera_tool_list(self):
335
- self.tools_list = []
336
- self.sorted_tools = []
337
-
338
- for toolbox in self.bera_tools['toolbox']:
339
- category = []
340
- for item in toolbox['tools']:
341
- if item['name']:
342
- category.append(item['name'])
343
- self.tools_list.append(item['name']) # add tool to list
344
-
345
- self.sorted_tools.append(category)
346
-
347
- def sort_toolboxes(self):
348
- for toolbox in self.toolbox_list:
349
- if toolbox.find('/') == (-1): # Does not contain a sub toolbox, i.e. does not contain '/'
350
- self.upper_toolboxes.append(toolbox) # add to both upper toolbox list and lower toolbox list
351
- self.lower_toolboxes.append(toolbox)
352
- else: # Contains a sub toolbox
353
- self.lower_toolboxes.append(toolbox) # add to only the lower toolbox list
354
-
355
- def get_bera_toolboxes(self):
356
- toolboxes = []
357
- for toolbox in self.bera_tools['toolbox']:
358
- tb = toolbox['category']
359
- toolboxes.append(tb)
360
-
361
- self.toolbox_list = toolboxes
362
-
363
- def get_bera_tool_params(self, tool_name):
364
- new_params = {'parameters': []}
365
- tool = {}
366
- batch_tool_list = []
367
- for toolbox in self.bera_tools['toolbox']:
368
- for single_tool in toolbox['tools']:
369
- if single_tool['batch_processing']:
370
- batch_tool_list.append(single_tool['name'])
371
-
372
- if tool_name == single_tool['name']:
373
- tool = single_tool
374
-
375
- for key, value in tool.items():
376
- if key != 'parameters':
377
- new_params[key] = value
378
-
379
- # convert json format for parameters
380
- if 'parameters' not in tool.keys():
381
- print('issue')
382
-
383
- for param in tool['parameters']:
384
- new_param = {'name': param['parameter']}
385
- if 'variable' in param.keys():
386
- new_param['flag'] = param['variable']
387
- # restore saved parameters
388
- saved_value = self.get_saved_tool_params(tool['tool_api'], param['variable'])
389
- if saved_value is not None:
390
- new_param['saved_value'] = saved_value
391
- else:
392
- new_param['flag'] = 'FIXME'
393
-
394
- if not param['output']:
395
- if param['type'] == 'list':
396
- if tool_name == 'Batch Processing':
397
- new_param['parameter_type'] = {'OptionList': batch_tool_list}
398
- new_param['data_type'] = 'String'
399
- else:
400
- new_param['parameter_type'] = {'OptionList': param['data']}
401
- new_param['data_type'] = 'String'
402
- if param['typelab'] == 'text':
403
- new_param['data_type'] = 'String'
404
- elif param['typelab'] == 'int':
405
- new_param['data_type'] = 'Integer'
406
- elif param['typelab'] == 'float':
407
- new_param['data_type'] = 'Float'
408
- elif param['typelab'] == 'bool':
409
- new_param['data_type'] = 'Boolean'
410
- elif param['type'] == 'text':
411
- new_param['parameter_type'] = 'String'
412
- elif param['type'] == 'number':
413
- if param['typelab'] == 'int':
414
- new_param['parameter_type'] = 'Integer'
415
- else:
416
- new_param['parameter_type'] = 'Float'
417
- elif param['type'] == 'file':
418
- new_param['parameter_type'] = {'ExistingFile': [param['typelab']]}
419
- else:
420
- new_param['parameter_type'] = {'ExistingFile': ''}
421
- else:
422
- new_param["parameter_type"] = {'NewFile': [param['typelab']]}
423
-
424
- new_param['description'] = param['description']
425
-
426
- if param['type'] == 'raster':
427
- for i in new_param["parameter_type"].keys():
428
- new_param['parameter_type'][i] = 'Raster'
429
- elif param['type'] == 'lidar':
430
- for i in new_param["parameter_type"].keys():
431
- new_param['parameter_type'][i] = 'Lidar'
432
- elif param['type'] == 'vector':
433
- for i in new_param["parameter_type"].keys():
434
- new_param['parameter_type'][i] = 'Vector'
435
- elif param['type'] == 'Directory':
436
- new_param['parameter_type'] = {'Directory': [param['typelab']]}
437
-
438
- new_param['default_value'] = param['default']
439
- if "optional" in param.keys():
440
- new_param['optional'] = param['optional']
441
- else:
442
- new_param['optional'] = False
443
-
444
- new_params['parameters'].append(new_param)
445
-
446
- return new_params
447
-
448
- def get_bera_tool_parameters_list(self, tool_name):
449
- params = self.get_bera_tool_params(tool_name)
450
- param_list = {}
451
- for item in params['parameters']:
452
- param_list[item['flag']] = item['default_value']
453
-
454
- return param_list
455
-
456
- def get_bera_tool_args(self, tool_name):
457
- params = self.get_bera_tool_params(tool_name)
458
- tool_args = params['parameters']
459
-
460
- return tool_args
461
-
462
- def get_bera_tool_name(self, tool_api):
463
- tool_name = None
464
- for toolbox in self.bera_tools['toolbox']:
465
- for tool in toolbox['tools']:
466
- if tool_api == tool['tool_api']:
467
- tool_name = tool['name']
468
-
469
- return tool_name
470
-
471
- def get_bera_tool_api(self, tool_name):
472
- tool_api = None
473
- for toolbox in self.bera_tools['toolbox']:
474
- for tool in toolbox['tools']:
475
- if tool_name == tool['name']:
476
- tool_api = tool['tool_api']
477
-
478
- return tool_api
479
-
480
- def get_bera_tool_type(self, tool_name):
481
- tool_type = None
482
- for toolbox in self.bera_tools['toolbox']:
483
- for tool in toolbox['tools']:
484
- if tool_name == tool['name']:
485
- tool_type = tool['tool_type']
486
-
487
- return tool_type
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 main interface for GUI related settings.
14
+ """
15
+ import json
16
+ import os
17
+ import platform
18
+ from collections import OrderedDict
19
+ from pathlib import Path
20
+
21
+ import beratools.core.constants as bt_const
22
+
23
+ running_windows = platform.system() == 'Windows'
24
+ BT_SHOW_ADVANCED_OPTIONS = False
25
+
26
+
27
+ def default_callback(value):
28
+ """
29
+ Define default callback that outputs using the print function.
30
+
31
+ When tools are called without providing a custom callback, this function
32
+ will be used to print to standard output.
33
+ """
34
+ print(value)
35
+
36
+
37
+ class BTData(object):
38
+ """An object for interfacing with the BERA Tools executable."""
39
+
40
+ def __init__(self):
41
+ if running_windows:
42
+ self.ext = '.exe'
43
+ else:
44
+ self.ext = ''
45
+ self.current_file_path = Path(__file__).resolve().parent
46
+
47
+ self.work_dir = ""
48
+ self.user_folder = Path('')
49
+ self.data_folder = Path('')
50
+ self.verbose = True
51
+ self.show_advanced = BT_SHOW_ADVANCED_OPTIONS
52
+ self.max_procs = -1
53
+ self.recent_tool = None
54
+ self.ascii_art = None
55
+ self.get_working_dir()
56
+ self.get_user_folder()
57
+
58
+ # set maximum available cpu core for tools
59
+ self.max_cpu_cores = os.cpu_count()
60
+
61
+ # load bera tools
62
+ self.tool_history = []
63
+ self.settings = {}
64
+ self.bera_tools = None
65
+ self.tools_list = []
66
+ self.sorted_tools = []
67
+ self.upper_toolboxes = []
68
+ self.lower_toolboxes = []
69
+ self.toolbox_list = []
70
+ self.get_bera_tools()
71
+ self.get_bera_tool_list()
72
+ self.get_bera_toolboxes()
73
+ self.sort_toolboxes()
74
+
75
+ self.setting_file = None
76
+ self.get_data_folder()
77
+ self.get_setting_file()
78
+ self.gui_setting_file = Path(self.current_file_path).joinpath(
79
+ bt_const.ASSETS_PATH, r"gui.json"
80
+ )
81
+
82
+ self.load_saved_tool_info()
83
+ self.load_gui_data()
84
+ self.get_tool_history()
85
+
86
+ self.default_callback = default_callback
87
+ self.start_minimized = False
88
+
89
+ def set_bera_dir(self, path_str):
90
+ """Set the directory to the BERA Tools executable file."""
91
+ self.current_file_path = path_str
92
+
93
+ def add_tool_history(self, tool, params):
94
+ if 'tool_history' not in self.settings:
95
+ self.settings['tool_history'] = OrderedDict()
96
+
97
+ self.settings['tool_history'][tool] = params
98
+ self.settings['tool_history'].move_to_end(tool, last=False)
99
+
100
+ def remove_tool_history_item(self, index):
101
+ key = list(self.settings['tool_history'].keys())[index]
102
+ self.settings['tool_history'].pop(key)
103
+ self.save_tool_info()
104
+
105
+ def remove_tool_history_all(self):
106
+ self.settings.pop("tool_history")
107
+ self.save_tool_info()
108
+
109
+ def save_tool_info(self):
110
+ if self.recent_tool:
111
+ if 'gui_parameters' not in self.settings.keys():
112
+ self.settings['gui_parameters'] = {}
113
+
114
+ self.settings['gui_parameters']['recent_tool'] = self.recent_tool
115
+
116
+ with open(self.setting_file, 'w') as file_setting:
117
+ try:
118
+ json.dump(self.settings, file_setting, indent=4)
119
+ except json.decoder.JSONDecodeError:
120
+ pass
121
+
122
+ def save_setting(self, key, value):
123
+ # check setting directory existence
124
+ data_path = Path(self.setting_file).resolve().parent
125
+ if not data_path.exists():
126
+ data_path.mkdir()
127
+
128
+ self.load_saved_tool_info()
129
+
130
+ if value is not None:
131
+ if 'gui_parameters' not in self.settings.keys():
132
+ self.settings['gui_parameters'] = {}
133
+
134
+ self.settings['gui_parameters'][key] = value
135
+
136
+ with open(self.setting_file, 'w') as write_settings_file:
137
+ json.dump(self.settings, write_settings_file, indent=4)
138
+
139
+ def get_working_dir(self):
140
+ current_file = Path(__file__).resolve()
141
+ btool_dir = current_file.parents[1]
142
+ self.work_dir = btool_dir
143
+
144
+ def get_user_folder(self):
145
+ self.user_folder = Path.home().joinpath('.beratools')
146
+ if not self.user_folder.exists():
147
+ self.user_folder.mkdir()
148
+
149
+ def get_data_folder(self):
150
+ self.data_folder = self.user_folder.joinpath('.data')
151
+ if not self.data_folder.exists():
152
+ self.data_folder.mkdir()
153
+
154
+ def get_logger_file_name(self, name):
155
+ if not name:
156
+ name = 'beratools'
157
+
158
+ logger_file_name = self.user_folder.joinpath(name).with_suffix('.log')
159
+ return logger_file_name.as_posix()
160
+
161
+ def get_setting_file(self):
162
+ self.setting_file = self.data_folder.joinpath('saved_tool_parameters.json')
163
+
164
+ def set_max_procs(self, val=-1):
165
+ """Set the maximum cores to use."""
166
+ self.max_procs = val
167
+ self.save_setting('max_procs', val)
168
+
169
+ def get_max_procs(self):
170
+ return self.max_procs
171
+
172
+ def get_max_cpu_cores(self):
173
+ return self.max_cpu_cores
174
+
175
+ def run_tool(self, tool_api, args, callback=None):
176
+ """
177
+ Run a tool and specifies tool arguments.
178
+
179
+ Returns 0 if completes without error.
180
+ Returns 1 if error encountered (details are sent to callback).
181
+ Returns 2 if process is cancelled by user.
182
+ """
183
+ try:
184
+ if callback is None:
185
+ callback = self.default_callback
186
+ except Exception as err:
187
+ callback(str(err))
188
+ return 1
189
+
190
+ # Call script using new process to make GUI responsive
191
+ try:
192
+ # convert to valid json string
193
+ args_string = str(args).replace("'", '"')
194
+ args_string = args_string.replace('True', 'true')
195
+ args_string = args_string.replace('False', 'false')
196
+
197
+ tool_name = self.get_bera_tool_name(tool_api)
198
+ tool_type = self.get_bera_tool_type(tool_name)
199
+ tool_args = None
200
+
201
+ if tool_type == 'python':
202
+ tool_args = [self.work_dir.joinpath(f'tools/{tool_api}.py').as_posix(),
203
+ '-i', args_string, '-p', str(self.get_max_procs()),
204
+ '-v', str(self.verbose)]
205
+ elif tool_type == 'executable':
206
+ print(globals().get(tool_api))
207
+ tool_args = globals()[tool_api](args_string)
208
+ lapis_path = self.work_dir.joinpath('./third_party/Lapis_0_8')
209
+ os.chdir(lapis_path.as_posix())
210
+ except Exception as err:
211
+ callback(str(err))
212
+ return 1
213
+
214
+ return tool_type, tool_args
215
+
216
+ def about(self):
217
+ """Retrieve the description for BERA Tools."""
218
+ try:
219
+ about_text = 'BERA Tools provide a series of tools developed by AppliedGRG lab.\n\n'
220
+ about_text += self.ascii_art
221
+ return about_text
222
+ except (OSError, ValueError) as err:
223
+ return err
224
+
225
+ def license(self):
226
+ """Retrieve the license information for BERA Tools."""
227
+ try:
228
+ with open(Path(self.current_file_path).joinpath(r'..\..\LICENSE.txt'), 'r') as f:
229
+ ret = f.read()
230
+
231
+ return ret
232
+ except (OSError, ValueError) as err:
233
+ return err
234
+
235
+ def load_saved_tool_info(self):
236
+ data_path = Path(self.setting_file).parent
237
+ if not data_path.exists():
238
+ data_path.mkdir()
239
+
240
+ saved_parameters = {}
241
+ json_file = Path(self.setting_file)
242
+ if not json_file.exists():
243
+ return
244
+
245
+ with open(json_file) as open_file:
246
+ try:
247
+ saved_parameters = json.load(open_file, object_pairs_hook=OrderedDict)
248
+ except json.decoder.JSONDecodeError:
249
+ pass
250
+
251
+ self.settings = saved_parameters
252
+
253
+ # parse file
254
+ if 'gui_parameters' in self.settings.keys():
255
+ gui_settings = self.settings['gui_parameters']
256
+
257
+ if 'max_procs' in gui_settings.keys():
258
+ self.max_procs = gui_settings['max_procs']
259
+
260
+ if 'recent_tool' in gui_settings.keys():
261
+ self.recent_tool = gui_settings['recent_tool']
262
+ if not self.get_bera_tool_api(self.recent_tool):
263
+ self.recent_tool = None
264
+
265
+ def load_gui_data(self):
266
+ gui_settings = {}
267
+ if not self.gui_setting_file.exists():
268
+ print("gui.json not exist.")
269
+ else:
270
+ # read the settings.json file if it exists
271
+ with open(self.gui_setting_file, 'r') as file_gui:
272
+ try:
273
+ gui_settings = json.load(file_gui)
274
+ except json.decoder.JSONDecodeError:
275
+ pass
276
+
277
+ # parse file
278
+ if 'ascii_art' in gui_settings.keys():
279
+ bera_art = ''
280
+ for line_of_art in gui_settings['ascii_art']:
281
+ bera_art += line_of_art
282
+ self.ascii_art = bera_art
283
+
284
+ def get_tool_history(self):
285
+ tool_history = []
286
+ self.load_saved_tool_info()
287
+ if self.settings:
288
+ if 'tool_history' in self.settings:
289
+ tool_history = self.settings['tool_history']
290
+
291
+ if tool_history:
292
+ self.tool_history = []
293
+ for item in tool_history:
294
+ item = self.get_bera_tool_name(item)
295
+ self.tool_history.append(item)
296
+
297
+ def get_saved_tool_params(self, tool_api, variable=None):
298
+ self.load_saved_tool_info()
299
+
300
+ if 'tool_history' in self.settings:
301
+ if tool_api in list(self.settings['tool_history']):
302
+ tool_params = self.settings['tool_history'][tool_api]
303
+ if tool_params:
304
+ if variable:
305
+ if variable in tool_params.keys():
306
+ saved_value = tool_params[variable]
307
+ return saved_value
308
+ else: # return all params
309
+ return tool_params
310
+
311
+ return None
312
+
313
+ def get_bera_tools(self):
314
+ tool_json = Path(self.current_file_path).joinpath(bt_const.ASSETS_PATH, r'beratools.json')
315
+ if tool_json.exists():
316
+ tool_json = open(Path(self.current_file_path).joinpath(bt_const.ASSETS_PATH, r'beratools.json'))
317
+ self.bera_tools = json.load(tool_json)
318
+ else:
319
+ print('Tool configuration file not exists')
320
+
321
+ def get_bera_tool_list(self):
322
+ self.tools_list = []
323
+ self.sorted_tools = []
324
+
325
+ for toolbox in self.bera_tools['toolbox']:
326
+ category = []
327
+ for item in toolbox['tools']:
328
+ if item['name']:
329
+ category.append(item['name'])
330
+ self.tools_list.append(item['name']) # add tool to list
331
+
332
+ self.sorted_tools.append(category)
333
+
334
+ def sort_toolboxes(self):
335
+ for toolbox in self.toolbox_list:
336
+ # Does not contain a sub toolbox, i.e. does not contain '/'
337
+ if toolbox.find('/') == (-1):
338
+ # add to both upper toolbox list and lower toolbox list
339
+ self.upper_toolboxes.append(toolbox)
340
+ self.lower_toolboxes.append(toolbox)
341
+ else: # Contains a sub toolbox
342
+ self.lower_toolboxes.append(toolbox) # add to the lower toolbox list
343
+
344
+ def get_bera_toolboxes(self):
345
+ toolboxes = []
346
+ for toolbox in self.bera_tools['toolbox']:
347
+ tb = toolbox['category']
348
+ toolboxes.append(tb)
349
+
350
+ self.toolbox_list = toolboxes
351
+
352
+ def get_bera_tool_params(self, tool_name):
353
+ new_param_whole = {'parameters': []}
354
+ tool = {}
355
+ batch_tool_list = []
356
+ for toolbox in self.bera_tools['toolbox']:
357
+ for single_tool in toolbox['tools']:
358
+ if single_tool['batch_processing']:
359
+ batch_tool_list.append(single_tool['name'])
360
+
361
+ if tool_name == single_tool['name']:
362
+ tool = single_tool
363
+
364
+ for key, value in tool.items():
365
+ if key != 'parameters':
366
+ new_param_whole[key] = value
367
+
368
+ # convert json format for parameters
369
+ if 'parameters' not in tool.keys():
370
+ print('issue')
371
+
372
+ for param in tool['parameters']:
373
+ single_param = {'name': param['parameter']}
374
+ if 'variable' in param.keys():
375
+ single_param['flag'] = param['variable']
376
+ # restore saved parameters
377
+ saved_value = self.get_saved_tool_params(tool['tool_api'], param['variable'])
378
+ if saved_value is not None:
379
+ single_param['saved_value'] = saved_value
380
+ else:
381
+ single_param['flag'] = 'FIXME'
382
+
383
+ single_param['output'] = param['output']
384
+ if not param['output']:
385
+ if param['type'] == 'list':
386
+ if tool_name == 'Batch Processing':
387
+ single_param['parameter_type'] = {'OptionList': batch_tool_list}
388
+ single_param['data_type'] = 'String'
389
+ else:
390
+ single_param['parameter_type'] = {'OptionList': param['data']}
391
+ single_param['data_type'] = 'String'
392
+ if param['typelab'] == 'text':
393
+ single_param['data_type'] = 'String'
394
+ elif param['typelab'] == 'int':
395
+ single_param['data_type'] = 'Integer'
396
+ elif param['typelab'] == 'float':
397
+ single_param['data_type'] = 'Float'
398
+ elif param['typelab'] == 'bool':
399
+ single_param['data_type'] = 'Boolean'
400
+ elif param['type'] == 'text':
401
+ single_param['parameter_type'] = 'String'
402
+ elif param['type'] == 'number':
403
+ if param['typelab'] == 'int':
404
+ single_param['parameter_type'] = 'Integer'
405
+ else:
406
+ single_param['parameter_type'] = 'Float'
407
+ elif param['type'] == 'file':
408
+ single_param['parameter_type'] = {'ExistingFile': [param['typelab']]}
409
+ else:
410
+ single_param['parameter_type'] = {'ExistingFile': ''}
411
+ else:
412
+ single_param["parameter_type"] = {'NewFile': [param['typelab']]}
413
+
414
+ single_param['description'] = param['description']
415
+
416
+ if param['type'] == 'raster':
417
+ for i in single_param["parameter_type"].keys():
418
+ single_param['parameter_type'][i] = 'Raster'
419
+ elif param['type'] == 'lidar':
420
+ for i in single_param["parameter_type"].keys():
421
+ single_param['parameter_type'][i] = 'Lidar'
422
+ elif param['type'] == 'vector':
423
+ for i in single_param["parameter_type"].keys():
424
+ single_param['parameter_type'][i] = 'Vector'
425
+ if 'layer' in param.keys():
426
+ layer_value = self.get_saved_tool_params(tool['tool_api'], param['layer'])
427
+ single_param['layer'] = {'layer_name': param['layer'], 'layer_value': layer_value}
428
+
429
+ elif param['type'] == 'Directory':
430
+ single_param['parameter_type'] = {'Directory': [param['typelab']]}
431
+
432
+ single_param['default_value'] = param['default']
433
+ if "optional" in param.keys():
434
+ single_param['optional'] = param['optional']
435
+ else:
436
+ single_param['optional'] = False
437
+
438
+ new_param_whole['parameters'].append(single_param)
439
+
440
+ return new_param_whole
441
+
442
+ def get_bera_tool_parameters_list(self, tool_name):
443
+ params = self.get_bera_tool_params(tool_name)
444
+ param_list = {}
445
+ for item in params['parameters']:
446
+ param_list[item['flag']] = item['default_value']
447
+
448
+ return param_list
449
+
450
+ def get_bera_tool_args(self, tool_name):
451
+ params = self.get_bera_tool_params(tool_name)
452
+ tool_args = params['parameters']
453
+
454
+ return tool_args
455
+
456
+ def get_bera_tool_name(self, tool_api):
457
+ tool_name = None
458
+ for toolbox in self.bera_tools['toolbox']:
459
+ for tool in toolbox['tools']:
460
+ if tool_api == tool['tool_api']:
461
+ tool_name = tool['name']
462
+
463
+ return tool_name
464
+
465
+ def get_bera_tool_api(self, tool_name):
466
+ tool_api = None
467
+ for toolbox in self.bera_tools['toolbox']:
468
+ for tool in toolbox['tools']:
469
+ if tool_name == tool['name']:
470
+ tool_api = tool['tool_api']
471
+
472
+ return tool_api
473
+
474
+ def get_bera_tool_type(self, tool_name):
475
+ tool_type = None
476
+ for toolbox in self.bera_tools['toolbox']:
477
+ for tool in toolbox['tools']:
478
+ if tool_name == tool['name']:
479
+ tool_type = tool['tool_type']
480
+
481
+ return tool_type