Surface buffer
The current state of a volcano is extremely variable with time and it has no meaning to define fixed risk zones around volcanoes (e.g. “from 5 to 10km from the crater”)
Currently, during the emergency situation in volcano eruption case, most of decision maker with help from the technical team define the safe zone from the crater without considering the surface (blue line in below picture).
Despite there is no default distance for volcanic safe zone, but it is important to calculate the true distance (considering the surface) from the crater — red line on above picture. For saving more lives and better preparedness.
Using GIS technology, we can calculate the true distance very easily. In the ESRI GeoNet forum, there has been discussions on how to calculate a surface buffer using ArcPy. What we need is a coordinate of selected location and the surface condition from the surrounding areas, we can use free elevation data from SRTM and can be downloaded from this site: 30-Meter SRTM Tile Downloader.
Picture above is the result, red line is the true distance of 12km from the crater considering the topographic condition. You can try to calculate with your own data using below script from Xander Bakker of ESRI Colombia below:
import arcpy import os import math # get SA license arcpy.CheckOutExtension(“Spatial”) # settings, input data max_dist = 12000 steps = 360 # 1 point each degree ws = r”Z:\Temp\SurfaceBuffer\Bali_UTM.gdb” dem = r”Z:\Temp\SurfaceBuffer\Bali_UTM.gdb\DEM” fc_pnt= r”Z:\Temp\SurfaceBuffer\Bali_UTM.gdb\Mt_Agung” # output and intermediate names slp_per_name = “slope_perc” fc_buf_name = “pnt_buf” fc_linepnt_name = “rad_pnt” fc_linepntdem_name = “rad_pnt_dem” fc_linepntdemsel_name = “rad_pnt_dem_sel” fc_buf3D_name = “buf_3D” # internal settings arcpy.env.workspace = ws arcpy.env.overwriteOutput = True # ws_mem = “IN_MEMORY” fld_rasval = “RASTERVALUE” # use cellsize as interval ##ras_dem = arcpy.Raster(dem) ##cellw = ras_dem.meanCellWidth ##cellh = ras_dem.meanCellHeight ##pixsize = int((cellw + cellh) / 2) #overwrite precision along line pixsize = 30.92336053 steps2 = max_dist / pixsize # Get point from input fc with arcpy.da.SearchCursor(fc_pnt, (“SHAPE@XY”)) as curs: for row in curs: pnt_cc = arcpy.Point(row[0][0],row[0][1]) break # Createa normal buffer on point with max distance fc_buf = os.path.join(ws, fc_buf_name) arcpy.Buffer_analysis(fc_pnt, fc_buf, “{0} METERS”.format(max_dist)) # get buffer geometry with arcpy.da.SearchCursor(fc_buf, (“SHAPE@”)) as curs: for row in curs: geom = row[0] break # get outline polyline = geom.boundary() length = polyline.length interval = length / steps # create new featureclass to hold points on lines sr = arcpy.Describe(fc_pnt).spatialReference arcpy.CreateFeatureclass_management(ws, fc_linepnt_name, “POINT”, “”, “DISABLED”, “DISABLED”, sr) fc_linepnt = os.path.join(ws, fc_linepnt_name) # add columns fld_id = “LineID” fld_dist = “Distance” fld_slpper = “SlopePerc” fld_dist3D = “Dist3D” fld_id2 = “LinePntID” arcpy.AddField_management(fc_linepnt, fld_id, “LONG”) arcpy.AddField_management(fc_linepnt, fld_dist, “DOUBLE”) arcpy.AddField_management(fc_linepnt, fld_slpper, “DOUBLE”) arcpy.AddField_management(fc_linepnt, fld_dist3D, “DOUBLE”) arcpy.AddField_management(fc_linepnt, fld_id2, “TEXT”, 25) flds = (“SHAPE@”, fld_id, fld_dist, fld_id2) with arcpy.da.InsertCursor(fc_linepnt, flds) as curs: # loop over boundary for i in range(0, steps): d = i * interval line_id = i # extract point on buffer outline pnt = polyline.positionAlongLine(d, False) # create line from center to pnt on line arr = arcpy.Array() arr.removeAll arr.add(pnt_cc) arr.add(pnt.firstPoint) line = arcpy.Polyline(arr) # extract points on line for j in range(0, int(steps2)): dist = int(j * pixsize) pnt2 = line.positionAlongLine(dist, False) # add to fc in mem with line ID and eucledian distance val_id = “{0}_{1}”.format(line_id, dist) curs.insertRow((pnt2, line_id, dist, val_id)) # extract DEM values fc_linepntdem = os.path.join(ws, fc_linepntdem_name) arcpy.sa.ExtractValuesToPoints(fc_linepnt, dem, fc_linepntdem, “INTERPOLATE”, “VALUE_ONLY”) # create nested dictionary for update ##flds = “;”.join([fld_id, fld_dist, fld_rasval, fld_slpper, fld_dist3D]) ##rows = arcpy.SearchCursor(fc_linepntdem, fields=flds, sort_fields=”{0} A; {1} A”.format(fld_id, fld_dist)) ##dct_vals = {“{0}_{1}”.format(r.getValue(fld_id), r.getValue(fld_dist)):[r.getValue(fld_id), r.getValue(fld_dist), r.getValue(fld_rasval), -1, -1] for r in rows} flds = (fld_id, fld_dist, fld_rasval, fld_id2) dct_vals = {r[3]:[r[0], r[1], r[2], -1, -1] for r in arcpy.da.SearchCursor(fc_linepntdem, flds)} # create list of sort order data flds = “;”.join([fld_id, fld_dist, fld_id2]) rows = arcpy.SearchCursor(fc_linepntdem, fields=flds, sort_fields=”{0} A; {1} A”.format(fld_id, fld_dist)) lst_vals_ids = [r.getValue(fld_id2) for r in rows] # save for each line the id of the outside point dct_res = {} # do some magic… for val_id in lst_vals_ids: if val_id in dct_vals: lst = dct_vals[val_id] line_id = lst[0] if lst[1] == 0: # start of line lst[3] = 0 lst[4] = 0 h0 = lst[2] dct_vals[val_id] = lst distcum = 0 else: # other points h1 = lst[2] h_dif = abs(h1-h0) dist3D = math.sqrt((h_dif**2) + (pixsize**2)) slope = h_dif * 100.0 / pixsize distcum += dist3D lst[3] = slope lst[4] = distcum dct_vals[val_id] = lst h0 = h1 if distcum <= max_dist: dct_res[line_id] = val_id # write slope and distance cum to featureclass flds = (fld_id, fld_dist, fld_slpper, fld_dist3D) with arcpy.da.UpdateCursor(fc_linepntdem, flds) as curs: for row in curs: val_id = “{0}_{1}”.format(row[0], row[1]) if val_id in dct_vals: lst = dct_vals[val_id] row[2] = lst[3] row[3] = lst[4] curs.updateRow(row) # select points within max distance arcpy.MakeFeatureLayer_management(fc_linepntdem, “lyr”) # define where clause based on list of id’s txt_ids = (str(dct_res.values()))[1:-1] txt_ids = txt_ids.replace(‘u’,’’) where = “{0} IN ({1})”.format(arcpy.AddFieldDelimiters(fc_linepntdem, fld_id2), txt_ids) arcpy.SelectLayerByAttribute_management(“lyr”, “NEW_SELECTION”, where) # copy to new fc (for debug) fc_linepntdemsel = os.path.join(ws, fc_linepntdemsel_name) arcpy.CopyFeatures_management(“lyr”, fc_linepntdemsel) # create convexHull on points for 3D buffer # this will give erroneous results on concave parts ##cnt = 0 ##with arcpy.da.SearchCursor(fc_linepntdemsel, (“SHAPE@”)) as curs: ## for row in curs: ## if cnt == 0: ## pntg = row[0] ## else: ## pntg_tmp = row[0] ## pntg = pntg.union(pntg_tmp) ## cnt += 1 ## ##pol = pntg.convexHull() # create polygon manually, sort points on line_id arr_pol = arcpy.Array() fld_shape = arcpy.Describe(fc_linepntdemsel).shapeFieldName flds = “;”.join([fld_id, fld_shape]) rows = arcpy.SearchCursor(fc_linepntdemsel, fields=flds, sort_fields=”{0} A”.format(fld_id)) cnt = 0 for row in rows: if cnt == 0: end_pntg = row.getValue(fld_shape) end_pnt = end_pntg.firstPoint pntg = row.getValue(fld_shape) pnt = pntg.firstPoint arr_pol.add(pnt) cnt += 1 arr_pol.add(end_pnt) del row del rows pol = arcpy.Polygon(arr_pol) # write polygon to output fc fc_buf3D = os.path.join(ws, fc_buf3D_name) arcpy.CreateFeatureclass_management(ws, fc_buf3D_name, “POLYGON”, “”, “DISABLED”, “DISABLED”, sr) with arcpy.da.InsertCursor(fc_buf3D, “SHAPE@”) as curs: row = (pol,) curs.insertRow(row) # return SA license arcpy.CheckInExtension(“Spatial”)
You need to adjust the distance in line 9 (in meters) and working directory in line 11–13. And then you can paste the code into python GUI inside ArcGIS, or run via command prompt.
Good luck!