-
Notifications
You must be signed in to change notification settings - Fork 5
/
bal.py
371 lines (277 loc) · 11.6 KB
/
bal.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
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
"""
:mod:`bal` - Calculate the bushfire attack level (BAL)
===============================================================
This module is used to produce the bushfire attack level (BAL) for an area of
interest within Australia based on input vegetation and elevation datasets
as per Method 1 in the Australian Standard AS 3959 (2009) -- Construction of
buildings in bushfire-prone areas.
:moduleauthor: Tina Yang <[email protected]>
"""
import sys
import os
import inspect
import math
from os.path import join as pjoin
import arcpy
from calculate_bal import bal_cal
from utilities.sa_tools import extract_by_mask, reclassify, cal_slope_aspect
__version__ = '2.1'
def reclass_veg(veg, dem, output_folder, remap, mask):
"""
Reclassify the original vegetation into the categories classified as Table
2.3 in AS 3959 (2009).
:param veg: `file` the input vegetation
:param dem: `file` the input dem used as reference projection
:param output_folder: `str` the output folder
:param remap: `srt` the vegetation reclassification
:param mask: `file` the mask for the effective AOI
:return: `file` the reclassified vegetation
"""
arcpy.env.overwriteOutput = True
input_folder = os.path.dirname(veg)
arcpy.env.workspace = input_folder
veg_r_init = 'veg_r_init'
veg_r_proj = pjoin(input_folder, 'veg_r_pj')
veg_class_r = pjoin(output_folder, 'veg_r')
arcpy.AddMessage('Remap the vegetation into classes of 1 ~ 7 ...')
# Derive reclassifed veg...
reclassify(veg, remap, veg_r_init)
# project as dem and change cell size same as that of dem
dem_c = arcpy.GetRasterProperties_management(dem, "CELLSIZEX").getOutput(0)
arcpy.ProjectRaster_management(veg_r_init, veg_r_proj, dem, "#", dem_c)
if arcpy.Exists(veg_r_init):
arcpy.Delete_management(veg_r_init)
# get the AOI
extract_by_mask(veg_r_proj, mask, veg_class_r)
g_list = arcpy.ListRasters('g_g*')
if len(g_list) != 0:
for g_file in g_list:
arcpy.Delete_management(g_file)
if arcpy.Exists(veg_r_proj):
arcpy.Delete_management(veg_r_proj)
return veg_class_r
def get_slope_aspect(input_dem, output_folder, mask):
"""
Calculate the slope and aspect from the input DEM
:param input_dem: `file` the input DEM
:param output_folder: `str` the output folder
:param mask: `file` the mask for the effective AOI
:return: `file` the reclassified slope
:return: `file` the reclassified aspect
"""
arcpy.env.overwriteOutput = True
input_folder = os.path.dirname(input_dem)
arcpy.env.workspace = input_folder
dem_slope = pjoin(input_folder, 'slope')
dem_aspect = pjoin(input_folder, 'aspect')
# Derive slope and aspect ...
cal_slope_aspect(input_dem, dem_slope, dem_aspect)
aspect_rec_init = pjoin(input_folder, 'aspect_r_i')
slope_rec_init = pjoin(input_folder, 'slope_r_i')
aspect_rec = pjoin(output_folder, 'aspect_r')
slope_rec = pjoin(output_folder, 'slope_r')
# Derive reclassifed aspect...
arcpy.AddMessage('Remap the aspect into classes of 1 ~ 9 ...')
reclassify(dem_aspect, "-1 0 9;0 22.5 1;22.5 67.5 2;67.5 112.5 3;\
112.5 157.5 4;157.5 202.5 5;202.5 247.5 6;247.5 292.5 7;\
292.5 337.5 8;337.5 360 1", aspect_rec_init)
value_max = arcpy.GetRasterProperties_management(
dem_slope, "MAXIMUM").getOutput(0)
if float(value_max) < 20:
value_max = 20.0001
# remap is minimum inclusive but maxmum exclusive. using .0001 to comform
# to the standard minimum is exclusive and maximum is inclusive
remap = "0 0 1;0.0001 5 2;5.0001 10 3;10.0001 15 4;\
15.0001 20 5;20.0001 " + \
str(math.ceil(float(value_max))) + " 6"
arcpy.AddMessage('Remap the slope into classes of 1 ~ 6 ...')
# Derive reclassifed slope...
reclassify(dem_slope, remap, slope_rec_init)
extract_by_mask(aspect_rec_init, mask, aspect_rec)
extract_by_mask(slope_rec_init, mask, slope_rec)
g_list = arcpy.ListRasters('g_g*')
if len(g_list) != 0:
for g_file in g_list:
arcpy.Delete_management(g_file)
if arcpy.Exists(dem_slope):
arcpy.Delete_management(dem_slope)
if arcpy.Exists(dem_aspect):
arcpy.Delete_management(dem_aspect)
if arcpy.Exists(slope_rec_init):
arcpy.Delete_management(slope_rec_init)
if arcpy.Exists(aspect_rec_init):
arcpy.Delete_management(aspect_rec_init)
return slope_rec, aspect_rec
def find_common_area(veg_class, slope, aspect):
"""
Find the common area of vegetation, slope and aspect to calculate BAL.
:param veg_class: `file` the reclassified vegetation
:param slope: `file` the slope derived from DEM
:param aspect: `file` the aspect derived from DEM
:return: `file` the vegetation in common area
:return: `file` the slope in common area
:return: `file` the aspect in common area
"""
output_folder = os.path.dirname(veg_class)
arcpy.env.overwriteOutput = True
# set directory
work_folder = output_folder
os.chdir(work_folder)
arcpy.env.workspace = work_folder
# get the common area of veg and dem
# get the extent of inputs
slope_poly = "slope_poly.shp"
veg_class_poly = "veg_class_poly.shp"
get_footprint(slope, slope_poly)
get_footprint(veg_class, veg_class_poly)
mask_com = 'mask_com.shp'
arcpy.Intersect_analysis([slope_poly, veg_class_poly], mask_com)
veg_class_com = pjoin(output_folder, 'veg_c')
slope_com = pjoin(output_folder, 'slope_c')
aspect_com = pjoin(output_folder, 'aspect_c')
extract_by_mask(veg_class, mask_com, veg_class_com)
extract_by_mask(slope, mask_com, slope_com)
extract_by_mask(aspect, mask_com, aspect_com)
if arcpy.Exists(slope_poly):
arcpy.Delete_management(slope_poly)
if arcpy.Exists(veg_class_poly):
arcpy.Delete_management(veg_class_poly)
if arcpy.Exists(mask_com):
arcpy.Delete_management(mask_com)
if arcpy.Exists(veg_class):
arcpy.Delete_management(veg_class)
if arcpy.Exists(slope):
arcpy.Delete_management(slope)
if arcpy.Exists(aspect):
arcpy.Delete_management(aspect)
return veg_class_com, slope_com, aspect_com
def bal_calc(vegetation, dem, fdi, output_folder, remap, mask):
"""
Calcuate BAL based on vegetation map and DEM.
:param vegetation: `file` the original vegetation
:param dem: `file` the input DEM
:param fdi: `int` the input FDI value
:param output_folder: `str` the output folder
:param remap: `srt` the vegetation reclassification
:param mask: `file` the mask for the effective area of interest (AOI)
"""
arcpy.env.overwriteOutput = True
arcpy.AddMessage('Reclassify the vegetation map ... ')
veg_class = reclass_veg(vegetation, dem, output_folder, remap, mask)
arcpy.AddMessage('Reclassify slope and aspect ... ')
slope, aspect = get_slope_aspect(dem, output_folder, mask)
if arcpy.Exists(mask):
arcpy.Delete_management(mask)
# extract the common area between vegtation, slope and aspect
arcpy.AddMessage('Get common area of input data ... ')
veg_class_com, slope_com, aspect_com = find_common_area(veg_class,
slope, aspect)
arcpy.AddMessage('Calculate the BAL ... ')
bal_cal(veg_class_com, slope_com, aspect_com, fdi)
def get_extent_mask(extent, mask):
"""
Derive the mask for the customised input extent.
:param extent: `str` the input extent
:param mask: `file` the output mask
"""
extent_list = str(extent).split()
# Array to hold points
array = arcpy.Array()
# Create the bounding box
array.add(arcpy.Point(float(extent_list[0]), float(extent_list[1])))
array.add(arcpy.Point(float(extent_list[2]), float(extent_list[1])))
array.add(arcpy.Point(float(extent_list[2]), float(extent_list[3])))
array.add(arcpy.Point(float(extent_list[0]), float(extent_list[3])))
array.add(arcpy.Point(float(extent_list[0]), float(extent_list[1])))
# Create the polygon object
polygon = arcpy.Polygon(array)
array.removeAll()
arcpy.CopyFeatures_management(polygon, mask)
def get_footprint(raster, footprint):
"""
Find the footprint of a raster
:param raster: `file` the input raster
:param footprint: `file` the output footprint
"""
# set the environment variable and workspace
arcpy.env.overwriteOutput = True
input_folder = os.path.dirname(raster)
arcpy.env.workspace = input_folder
raster_extent = arcpy.Describe(raster).extent
get_extent_mask(raster_extent, footprint)
# add the original spatial reference to the footprint
desc = arcpy.Describe(raster)
arcpy.DefineProjection_management(footprint, desc.spatialReference)
def find_aoi(extent, dem, veg):
"""
Find the effective area of interest based on input vegetation map, DEM and
extent.
:param extent: `str` the input extent
:param dem: `file` the input DEM
:param veg: `file` the input vegetation
:return: `file` the mask for effective AOI
"""
# set the environment variable and workspace
arcpy.env.overwriteOutput = True
input_dem_folder = os.path.dirname(dem)
input_veg_folder = os.path.dirname(veg)
arcpy.env.workspace = input_dem_folder
# derive the effective mask based on the input data
arcpy.AddMessage('Get the area of interest from the input extent ...')
mask = pjoin(input_dem_folder, 'mask.shp')
if str(extent) in ['DEFAULT', 'MAXOF', 'MINOF']:
# get the extent of inputs
dem_poly = pjoin(input_dem_folder, "dem_poly.shp")
veg_poly = pjoin(input_veg_folder, "veg_poly.shp")
get_footprint(dem, dem_poly)
get_footprint(veg, veg_poly)
arcpy.Intersect_analysis([dem_poly, veg_poly], mask)
# delete intermediate files
if arcpy.Exists(dem_poly):
arcpy.Delete_management(dem_poly)
if arcpy.Exists(veg_poly):
arcpy.Delete_management(veg_poly)
else:
get_extent_mask(extent, mask)
# add dem's spatial reference to the mask
desc = arcpy.Describe(dem)
arcpy.DefineProjection_management(mask, desc.spatialReference)
return mask
def run():
"""
Run the BAL calculations.
"""
# add subfolders into path
cmd_folder = os.path.realpath(
os.path.abspath(
os.path.split(
inspect.getfile(
inspect.currentframe()))[0]))
if cmd_folder not in sys.path:
sys.path.insert(0, cmd_folder)
cmd_subfolder = pjoin(cmd_folder, "utilities")
if cmd_subfolder not in sys.path:
sys.path.insert(0, cmd_subfolder)
# get input parameters from toolbox interface
dem = arcpy.GetParameterAsText(0)
veg = arcpy.GetParameterAsText(1)
remap = arcpy.GetParameterAsText(2)
output_folder = arcpy.GetParameterAsText(3)
fdi = arcpy.GetParameter(4)
extent = arcpy.GetParameter(5)
dem_sr = arcpy.Describe(dem).spatialReference
arcpy.AddMessage("DEM's spatial reference type is {0}".format(dem_sr.type))
if dem_sr.type == "Projected":
# find effective AOI based on the input parameters
mask = find_aoi(extent, dem, veg)
try:
# calculate the BAL for the effective AOI
bal_calc(veg, dem, fdi, output_folder, remap, mask)
arcpy.AddMessage("Successfully completed BAL calculation!")
except Exception as err:
# Report any exceptions back
arcpy.AddError(err)
else:
arcpy.AddError("To go ahead, the DEM needs to be projected first")
if __name__ == '__main__':
run()