From fd5ddea7f22e28c95679bf4cdd9eacb1c44454b1 Mon Sep 17 00:00:00 2001 From: Dan Howe Date: Mon, 2 Jul 2018 15:45:43 +1000 Subject: [PATCH] Add las_manipulation.py --- las_manipulation.py | 548 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 las_manipulation.py diff --git a/las_manipulation.py b/las_manipulation.py new file mode 100644 index 0000000..30537df --- /dev/null +++ b/las_manipulation.py @@ -0,0 +1,548 @@ +# 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