# 2017088 Central Coast job # this is aimed at recieving a large lidar file and #first cropping the lidar to a chosen shp (ie to a single beach) #second try to reclassify vegetation and buildings where possible #cropping the las file to remove the wave runup zone (and making a shapefile to crop it) --> This is a deliverable #make a raster at a given step value and crop by the above shapefile --> this is what will be used to extract all the values and do heat maps #cropping the las file to remove the wave runup zone (and making a shapefile to crop it) --> This is a deliverable # extract values along a given line --> This is a deliverable ########################### IMPORTS ########################################### import subprocess import pandas as pd import numpy as np import shapefile from scipy import interpolate from shapely.geometry import Point import os from tqdm import tqdm ############################################################################### ########################## FIXED INPUTS ####################################### #path to LasTools NOTE THERE CAN BE NO SPACES path_2_lastools='C:/ProgramData/chocolatey/' #input_file=r"J:\Project\wrl2017088 Central Coast Council Aerial Survey and Coastal Analysis\04_Working\Python\Parameter Files\Survey 2\las manipulation survey2.xlsx" # input_file=r"J:\Project\wrl2017088 Central Coast Council Aerial Survey and Coastal Analysis\04_Working\Python\Parameter Files\Survey 2\las manipulation patonga2.xlsx" input_file='Parameter Files/las manipulation survey2.xlsx' ##################### UNCOMMENT THIS SECTION IF YOU WANT TO DO INDIVIDUAL BEACHES######### ##STEP ONE #input_las1="C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Wamberal2013\\wamberal2013.las" #CANNOT HAVE SPACES #initial_crop_poly="C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\CC_Crop\\copacabana.shp" #temp_las1 = "C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Temp\\tmp1.las" # ##STEP TWO #input_las2 = temp_las1 #temp_las2= "C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Temp\\tmp2.las" #step_veg=5 #step size to remove vege. Probably don't change #temp_las3="C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Temp\\tmp3.las" #step_build=35 #step size to remove buildings. May need to change. bigger buildings mean bigger step etc # ##STEP THREE #input_las3=temp_las3 #temp_xyz= "C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Temp\\tmp_thinned.xyz" #zone_MGA=56 #assumes it is in MGA #check_value=1 #m AHD value above which you believe, shouldn't delete #direct = 'west_east' #at the moment can only be north_south or west_east, wont work if it is south_north #check_distance=1000 #maximum distance you can crop away from the desnified polygon (should probably be somewhere between 10 - 50m) #polygon_name="copacabana_crop" #path_2_poly="C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Results\\" #tmp_dir="C:\\Users\\z3331378\\Desktop\\LAStools\\XXFiles\\Temp" ############################################################################### ############################### SUB ROUTINES ################################## def check_output(command,console): if console == True: process = subprocess.Popen(command) else: process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) output,error = process.communicate() returncode = process.poll() return returncode,output def crop_las(las, shp, output, lastools_loc): # output is the full path and filename (inc extension) to put in path_2_lasclip=lastools_loc+"\\bin\\lasclip" command="%s -i %s -poly %s -o %s" % (path_2_lasclip, las, shp, output) returncode,output = check_output(command, False) if returncode!= 0: print("Error. lasclip failed on %s" % shp.split('//')[-1].split('.')[0]) else: return None def colour_las(las, tif, out_las, lastools_loc): # output is the full path and filename (inc extension) to put in path_2_lascolor=lastools_loc+"\\bin\\lascolor" command="%s -i %s -image %s -o %s" % (path_2_lascolor, las, tif, out_las) returncode,output = check_output(command, False) if returncode!= 0: print("Error. lascolor failed on") else: return None def remove_veg(las, output, lastools_loc, step=5): # output is the full path and filename (inc extension) to put in #step is how the features are removes - the step size should be able to distinguish between the features you are removing #I THINK that the way lasground works is to create a tin at the step size specified and find points that don't make sense # by comparing to the tin #to remove veg, a step size of 3 - 5 is typically used path_2_lasground=lastools_loc+"\\bin\\lasground_new" command="%s -i %s -o %s -step 5" % (path_2_lasground, las, output) returncode,output = check_output(command, False) if returncode!= 0: print("Error. lasground failed on %s" % las.split('\\')[-1].split('.')[0]) else: return None def remove_buildings(las, output, lastools_loc, step=50): # output is the full path and filename (inc extension) to put in #step is how the features are removes - the step size should be able to distinguish between the features you are removing #I THINK that the way lasground works is to create a tin at the step size specified and find points that don't make sense # by comparing to the tin #to remove buildings, a step size of 10 -50 is typically used #too high a step size will miss small buildings (eg garages, SLSC huts etc), too small will miss large buildings (ie stadiums and warehouses) # use a step size of ~25m for houses etc. path_2_lasground=lastools_loc+"\\bin\\lasground_new" command="%s -i %s -o %s -step %s" % (path_2_lasground, las, output, step) returncode,output = check_output(command, False) if returncode!= 0: print("Error. lasground failed on %s" % las.split('\\')[-1].split('.')[0]) else: return None def make_raster(las, output, lastools_loc, keep_only_ground=False): #not that keep ground only option only rasters points classified as "2" in the lidar (ie ground) #this effectively creates a "bare earth dem" #note that this should only be used after remove_veg and/or remove_buildings has been run path_2_blastdem=lastools_loc+"\\bin\\blast2dem" if keep_only_ground==False: command="%s -i %s -o %s -step 0.2" % (path_2_blastdem, las, output) else: command="%s -i %s -o %s -step 0.2 -keep_class 2" % (path_2_blastdem, las, output) returncode,output2 = check_output(command, False) if returncode!= 0: print("Error. blast2dem failed on %s" % las.split('\\')[-1].split('.')[0]) else: return None def make_xyz(las, output, lastools_loc, step=1): #to thin the las and create regular grid to allow the computation of the wave runup polygon #output must have location and .xyz file extension path_2_blastdem=lastools_loc+"\\bin\\blast2dem" command="%s -i %s -o %s -step %s" % (path_2_blastdem, las, output, step) returncode,output2 = check_output(command, False) if returncode!= 0: print("Error. blast2dem failed on %s" % las.split('\\')[-1].split('.')[0]) else: return None def remove_problems(x_list, y_list, z_list, x_now, y_now, check_value): z_ave=nine_pt_moving_average(z_list) deriv_ave, chainage=forward_derivative(x_list, y_list, z_ave) deriv_real, chainage=two_point_derivative(x_list, y_list, z_list) #first find the reference contour on the beach #index_contour, x_now, y_now, distance=find_beach_reference_contour_choose_closest(chainage, z_ave, x_list, y_list, x_now, y_now,deriv_ave, check_value) index_contour, x_now, y_now, distance=find_beach_reference_contour(chainage, z_ave, x_list, y_list, x_now, y_now,deriv_ave,deriv_real,check_value) if index_contournat_dev: break else: i=index_contour z_return=z_list[0:i] chainage_return=chainage[0:i] return z_return, chainage_return, x_now, y_now, distance def two_point_derivative(x_list, y_list, z_list): chain=[((x_list[0]-x_list[i])**2+(y_list[0]-y_list[i])**2)**0.5 for i in range(0,len(x_list))] deriv=[(z_list[i+1]-z_list[i-1])/(chain[i+1]-chain[i-1]) for i in range(1, len(z_list)-1)] deriv.insert(0,0) deriv.append(0) return deriv, chain def forward_derivative(x_list, y_list, z_list): chain=[((x_list[0]-x_list[i])**2+(y_list[0]-y_list[i])**2)**0.5 for i in range(0,len(x_list))] deriv=[(z_list[i]-z_list[i-1])/(chain[i]-chain[i-1]) for i in range(0, len(z_list)-1)] deriv.insert(0,0) return deriv, chain def find_first_over_reference(z_list, value): i=len(z_list)-1 while i>0 and z_list[i]len(z_list)-5: ave=np.mean([z_list[j] for j in range(i-4,len(z_list))]) else: ave=np.mean([z_list[j] for j in range(i-4,i+5)]) move_ave.append(ave) i=i+1 return move_ave def find_neg_derivative(z_list, deriv_list): i=len(z_list)-5 while z_list[i]>=0 and z_list[i+1]>=0 and z_list[i+2]>=0 and z_list[i+3]>=0 and z_list[i+4]>=0: i=i-1 return i def find_beach_reference_contour_choose_closest(chain_list, z_ave_list, x_list, y_list, x_last, y_last, deriv_ave_list, check_value): #note that z_list should be the 9 point moving average #assumes that beaches are shallow (|derivative|<0.3), sloping and between 0 - 4 m AHD i=0 choice_list=[] distance_list=[] if z_ave_list[i]>check_value: state_now='over' else: state_now='under' while icheck_value: #only keep if it is downward sloping state_now='over' elif state_now=='over' and z_ave_list[i]0 and x_last==None: #choose the first time for the first point i=choice_list[0] distance=0 elif len(choice_list)>0 and x_last!=None: assert(len(choice_list)==len(distance_list)) i=choice_list[distance_list.index(min(distance_list))] distance=min(distance_list) if i>=len(x_list): i=len(x_list)-1 if x_last!=None: distance=((x_last - x_list[i])**2+(y_last - y_list[i])**2)**0.5 else: distance=0 x=x_list[i] y=y_list[i] return i, x, y, distance def find_beach_reference_contour(chain_list, z_ave_list, x_list, y_list, x_last, y_last, deriv_ave_list,deriv_real_list, check_value): #note that z_list should be the 9 point moving average #assumes that beaches are shallow (|derivative|<0.3), sloping and between 0 - 4 m AHD i=len(z_ave_list)-1 while i>=0 and (z_ave_list[i]>check_value+2 or z_ave_list[i]0 or max([abs(i) for i in deriv_real_list[max(0,i-7):i]]+[0])>0.3):#beaches are shallow sloping, low i=i-1 #find the first time it gets to check_value after this while i>=0 and z_ave_list[i]0 and z_ave_list[i]10: Z_set, chainage_tmp, temp_x, temp_y, distance=remove_problems(X_tmp, Y_tmp, Z_tmp,tmp_x_last, tmp_y_last, set_check_value) #except: else: Z_set=Z_tmp temp_x=X_tmp[len(Z_set)-1] temp_y=Y_tmp[len(Z_set)-1] distance=0 distance_2_old=distance_point_to_poly(X_tmp, Y_tmp, temp_x, temp_y) if distance_2_old10: Z_set, chainage_tmp, temp_x, temp_y, distance=remove_problems(X_tmp, Y_tmp, Z_tmp,tmp_x_last, tmp_y_last, set_check_value) #except: else: Z_set=Z_tmp temp_x=X_tmp[len(Z_set)-1] temp_y=Y_tmp[len(Z_set)-1] distance=0 X_set=X_tmp[0:len(Z_set)] Y_set=Y_tmp[0:len(Z_set)] #write to new data frame #if len(Z_set)>0: # for i in range(0, len(Z_set)): # processed_data =processed_data.append({'X':X_set[i],'Y':Y_set[i],'Z':Z_set[i],'r':r_set[i],'g':g_set[i],'b':b_set[i]}, ignore_index=True) #add to crop line distance_2_old=distance_point_to_poly(X_tmp, Y_tmp, temp_x, temp_y) if distance_2_old