From 1e456756117127a05b6e9a68aee7fb6e3689eb7d Mon Sep 17 00:00:00 2001 From: Chris Leaman Date: Mon, 4 Mar 2019 15:46:58 +1100 Subject: [PATCH] Optimize calculation of profile volume difference --- src/analysis/observed_storm_impacts.py | 65 ++++++++++++++++++-------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/analysis/observed_storm_impacts.py b/src/analysis/observed_storm_impacts.py index b0653d5..78b9d8b 100644 --- a/src/analysis/observed_storm_impacts.py +++ b/src/analysis/observed_storm_impacts.py @@ -4,6 +4,7 @@ import pandas as pd from scipy.integrate import simps from logs import setup_logging +from utils import crossings logger = setup_logging() @@ -41,14 +42,9 @@ def volume_change(df_profiles, df_profile_features, zone): "Calculating change in beach volume at {} in {} zone".format(site_id, zone) ) - # TODO Change this query to an index - query = "site_id=='{}'&profile_type=='prestorm'".format(site_id) - prestorm_dune_toe_x = df_profile_features.query(query).dune_toe_x.tolist() - prestorm_dune_crest_x = df_profile_features.query(query).dune_crest_x.tolist() - - # We may not have a dune toe or crest defined, or there may be multiple defined. - prestorm_dune_crest_x = return_first_or_nan(prestorm_dune_crest_x) - prestorm_dune_toe_x = return_first_or_nan(prestorm_dune_toe_x) + prestorm_row = df_profile_features.loc[(site_id, "prestorm")] + prestorm_dune_toe_x = prestorm_row.dune_toe_x + prestorm_dune_crest_x = prestorm_row.dune_crest_x # If no dune toe has been defined, Dlow = Dhigh. Refer to Sallenger (2000). if np.isnan(prestorm_dune_toe_x): @@ -56,8 +52,7 @@ def volume_change(df_profiles, df_profile_features, zone): # If no prestorm and poststorm profiles, skip site and continue profile_lengths = [ - len(df_site.query("profile_type == '{}'".format(x))) - for x in ["prestorm", "poststorm"] + len(df_site.xs(x, level="profile_type")) for x in ["prestorm", "poststorm"] ] if any([length == 0 for length in profile_lengths]): continue @@ -113,20 +108,52 @@ def volume_change(df_profiles, df_profile_features, zone): x_max=x_max, ) - # Volume change needs to be calculated including a tolerance for LIDAR accuracy. If difference between - # profiles is less than 20 cm, consider them as zero difference. - prestorm_z = ( - df_zone.query("profile_type=='prestorm'").reset_index("profile_type").z + # Identify the x location where our pre and post storm profiles start to differ. This is so changes no due to + # the storm are not included when calculating volume. + df_prestorm = ( + df_site.xs("prestorm", level="profile_type").z.rename("z_pre").to_frame() ) - poststorm_z = ( - df_zone.query("profile_type=='poststorm'").reset_index("profile_type").z + df_poststorm = ( + df_site.xs("poststorm", level="profile_type").z.rename("z_post").to_frame() ) - diff_z = prestorm_z - poststorm_z - diff_z[abs(diff_z) < 0.2] = 0 + df_diff = df_prestorm.merge(df_poststorm, on=["site_id", "x"]) + df_diff["z_diff"] = df_diff.z_pre - df_diff.z_post + + # Find all locations where the difference in pre and post storm is zero. Take the most seaward location as the + # x location where our profiles are the same. + x_crossings = crossings(df_diff.index.get_level_values("x"), df_diff.z_diff, 0) + + if len(x_crossings) != 0: + x_change_point = x_crossings[-1] + else: + x_change_point = np.nan + + # # For debugging + # import matplotlib.pyplot as plt + # f,(ax1,ax2) = plt.subplots(2,1,sharex=True) + # ax1.plot(df_prestorm.index.get_level_values('x'), df_prestorm.z_pre,label='prestorm') + # ax1.plot(df_poststorm.index.get_level_values('x'), df_poststorm.z_post,label='poststorm') + # ax1.axvline(x_crossings[-1], color='red', linestyle='--', linewidth=0.5, label='Change point') + # ax1.legend() + # ax1.set_title(site_id) + # ax1.set_ylabel('elevation (m AHD)') + # ax2.plot(df_diff.index.get_level_values('x'), df_diff.z_diff) + # ax2.set_xlabel('x coordinate (m)') + # ax2.set_ylabel('elevation diff (m)') + # ax2.axvline(x_crossings[-1],color='red',linestyle='--',linewidth=0.5) + # plt.show() + diff_vol = beach_volume( - x=diff_z.index.get_level_values("x"), z=diff_z, x_min=x_min, x_max=x_max + x=df_diff.index.get_level_values("x"), + z=df_diff.z_diff, + x_min=np.nanmax([x_min, x_change_point]), + x_max=np.nanmax([x_max, x_change_point]), ) + # Here, if cannot calculate the difference volume, assume no volume change + if np.isnan(diff_vol): + diff_vol = 0 + df_vol_changes.loc[site_id, "prestorm_{}_vol".format(zone)] = prestorm_vol df_vol_changes.loc[site_id, "poststorm_{}_vol".format(zone)] = poststorm_vol df_vol_changes.loc[site_id, "{}_vol_change".format(zone)] = diff_vol