AfwGR#Qe`P{idn(q)^QB#r%dxk>n1$#BD}O8U0%yQJ=eA{${L2|1pIV`?7A
znGZ1s%Pnf;3zRz6XZWHkYMbcbOO$mKOOC~kitNA}eT9en3)G5H4VCUIJ(6^g3Zvwn
zEcOd3{gaZ84+gh&1uHCN!3@g2vU>`F-5cewB&qSk((>DABV0wGnX~#NQLDKCD}<;N
zv}?`cn=F$_xTa_HdHsTZMYrFzIW3FytTAb%Q98>+4EMDKLN>;Y_QI_nDWpT>n?~<+
zYFF@tUw{$?;A0;L*(w75jvAcUh#9yG!Yxx|)9Hhkf-kyuEs(AWKS#1h!}?npq$EJN
zM$JJjB3roVK!Q$8HakhU=-}9^fpEKN+*avCyF|Ba*T8~+T?0EP%COY|vINaL`~}j4
zX9V?dGvx>)ZifkVk}W50x0~&eY5X-NrkN7rKF5h%#c?K{IDbOSMw1k0zx;_n6=D~&^g30(x{tngqbqeUvFg?iMpxDe{(Ib%Wt8z_Tm~{_%ZnowB8)dp0EiB^P_FXRD#nOSnD)`lRX2W+ypKeB9ebi
z`5&X8MZr51{5}PLKmp;Pj1Yj$c|~xk=VMSw*dh!s1YHb?@{cG+I4J*^0>;5VL5z%)
zR;=*Aeo11|i)hbkcw#+z$YL(js|Rmv7_F!F;kz2bwM2J>d2sgIk5sh9gplrFTZ5
z)sH{}&U7-c21YugBJ*|Y%g`?Gy&jF$vQ?-mfU!|HXtDqdK^fJEdkQ?Y4kQg@)$r8qyCC6U+FltndmsiZV;RSf>Lml
zz+r*Q`Z;LMx?D%kv-@t5#uen8;+*@Ov(R^#G-i==nsdI)Ik|ogIYFRl)0(DfO|cqd
z808$Ed5mOoPK!=-#$Xw+`v$2e#Ug`kstbKSj@6)(S9Nh-T;O>+wbN0qcBXq4tuBhE
zy60%O0e?#(FD?$??`iztM7|2)P-(iSBVE^n#l7$5Cp9T>w{b^MNcY&jc=ti
zRV-7$s&AqcxaGSHTX4#(Q2O6ez{pD~E@LNV#aRq?ZzC0$`X*)G;W%B#lM^>SxW9$8
z_PIA=jsNz(#(x|7+a>bN!@bux>sZU&RV-j*gx~n!{Jk
zdy{lAi!ni`AOp$R+6KQY*vD=wf-H_CB{C=aJ
zZ;!%@9&PkLaWREoEd`L8BvwKMUMPPL!)Q-@?wOr|P8S}9{paN;XuLNNz~f98S|%=>
z4uJm!%1QW<2aWa&6=e?iHpQ3-{)6)EIx$YDY}(B$*9$3f?{HGAwtj~Su|sK_9ZCgO
zY~y&$3a?QKr|-R&32Of#1)t{()`Q_rV($~{#xcQ=KK!2S0gDw5JvYja$1Fly%90q%
zkq{j)4+9tRP)gRk3FQJLTNZFMU^pa(WW{fTWMJ=D;pTI2hPhge@KT9gcQh2aimZ*>~Uib<|
zjUE_Nxz2*7#t)>$O(bIB{UHru>Od~Zb%
literal 0
HcmV?d00001
diff --git a/src/tools/utils/__pycache__/shoreline_evolution.cpython-39.pyc b/src/tools/utils/__pycache__/shoreline_evolution.cpython-39.pyc
index 7b91ffa3b433bda554c23218430734291994104d..e230549c125a0080852c64151218167570af67fd 100644
GIT binary patch
delta 55
zcmaDN{Xm*Gk(ZZ?0SMe?G^GY?9ZQ7F#HFG|hH%u7`$$r1CjkEK65ap+
diff --git a/src/tools/utils/__pycache__/transect_processor.cpython-39.pyc b/src/tools/utils/__pycache__/transect_processor.cpython-39.pyc
index b4739e0da667051a103c21da7824edda21aa1fd2..3dbc1eab9529c2a0d76cca7a205af56a18b617c6 100644
GIT binary patch
literal 2894
zcmaJ@&2QvH7OxN6?e6qI7=?Tc5@aPLre^`opw$XNG|GG~LaXeE4*^zK%eb73I~}`I
zh}
zr+nzX(?i8SX1(A9me^KGa{dueHc`x*5E&!ukx*QTrwl7d<)MZ(6`=N2ONFQd)rQ56
zed!aO6edfn2f9o%V@jP$SeZT4rg8_xT!pClm`zyCVqObKls;E{jg2MsqAfOXNSKye
zwskv8bYAS*P~|&2r1U)NrRDI*3bkW9X{q)7B&|wqJsQ!I)-%~)M+aC)w;=w_G&%W~
zjl1t{SDC5S&BIb>gS^PrtFjzy8=Y=b!)zZ`Ib=0GOgx;O=4~t%#&KTcRUDH^L&llt
zh(H`)xp4TmXG2p?!=?{Sq2j#I#TT5^6?bwzLGbo`>O0lt9wM@aAi^_Z!bW_TjYK7D
z{)FFUU;el)MsnoUQu!-P1%3o!BLVxcp!a~h!30u_bjr>}S-CDK(
z%>IOPcJROFji2wM)uwj%Ci@LbHxF*AmYW$Onr&+JgPEmqs}>Wk!kt;9J6oEte)2aG
z|A(*(r*X*D2|4nFAgZZl{aeNEAUpXF+P%<4+REfBV_Q`j4{}r4c2*qjXF91eD+gI&
zgY0pY6$(n3S}!+w(F=4N)1zfmCAx~!he@%U#j`a{^wMeb9fBCZZFz2aY`y)&>^Vd=
zVu3bvDKEaxUbtuk@
zbYFlPw8fq`-vb;0H?Buc?~&JdH~$c(YD0#?9r4`^lRv_0Wkqsh1+01s~(n0Uq*M&Z(t>gp_N5a
z=rtOdc(se%cCc(S>;#@Tj;F4Eh7951eF7WB;tgiDuQTr6FCb5?
zO2u~tEdPBZYYEoWBOfdp)IPB)^xLBr^ssKBeQngP+ln1>{X-p&!VBRL6p~?LyPpmf0&(Y1
zBL(Ya%(Cr6*Y#2Ps6lX$42ML73);nOSOR%AOkqoxxw7Gda&SZ!0u-xta@6ZIY(U6~
zf*p6KZICt#yg~~|T|SCJn&ODp8-h)vkR<7m9K!N5JUS>69&Vr*f-P)4
z_jnft@Z{@gE%D=(v)Fo;S_!1HhwPtn$x|o@2YxLke1ReC#3Q^f1#@r$U4kJ<3q}AH
zMk0kGY(edFCGZH~;h*--k85U5$E~nYy9$ul!n!>XxEOq^7gDyU
z9@>^pRE}RH%RjgL3oFyoScxR0-@<0C|A3-^|0v&uBkBzEugj_ljqfr_)OJ-2Gm$7C
zqu}pn_!#9V5(m%^j?O#z53qb6#gMm|?D7}zUd4NS<(y0Bkg$cUNPMMxB(9O5>9&g`
zS>@@JAIbFGaxS_qu)oI#%1`e12tj;Va7}%^wmYbPA8qR+GY^k6We*z+lR_m1=TJ5%
fzv?5)I!bc0p0|VzHyR$iNlDSr(gYT~V)5FqiFu<`
literal 2664
zcma)8OLH4V5T4nWRvN$a3os}a;+LdQl
z#B!}J#0M@E6h9y(ANfmj<&?j`iLYm6S&nmHwz_A!XS!#mU$epVbcLaP{^7-FtIF6v
zL`*gd#2O&o01`~_HuH*}dtB3oXX@Va?5~+Hgn7t>896UauLRl>HfUSZuHX+@-FXd7
ztPv8PY$k{`K>9II#7Mhmh_cqmv4_^=S;7&eL+07Sg|3P^_echD8im=WOu{Hl6ZzVJ
zd`Q}k8;uF>0Mh$FIUlkkmNTE{1~6ck3%q_Sv*vN}-^dozT6V))yTQD&M6ZyM-?
zS2vZFM(w67;mFbj`kTpVbpNhx+}y%_`?z*e`>vH{!Na7z^(aZ&+sS^EZe?`caIIUf}^><--=tA@00FShk#Kr9AnOy!%=KBSgre
zw%v1UlA2=A*?KW=tRg;OJ1elEpi&z(y6K8&{&fciPd_r?szYrO3w1dvJ2x|K`Ys>q!TeW2^cG}9dPYPuxKB-+RIHwI?uO;%7?&Cw&NcZa7
zb$|9hb&nO-$0KROc&a*i^G4m6@H>~t&BFOQ0^|${oDbVUnzovK|JcdtdGJ$80OlTC
zIqSx=0%sgb39l37Ck!TBh}2$X8Q-O3VY{ZDF&DvYg0CrKk?mTProZ!oKH+N`fw54m
zbPxiRKBb1F?Yv4Hy%?pDoCQNU?N&^g#Ff*DvS*1^7s*DUZvA_=~u+sN2nNm(MMEn9@Fy3aavto@pmlX1Khf*jGI+5?I
zitl$4(QDJV>ic`Wpk2(!cd%5FPvjCIeWDXYPL$*<2apnSd1cD2XkG~NRHJRz0BH?q
zTpv-ZPz}w<5~z@e>?KEKv|(CD%{)jl8020QWIY+Brz#>PJVlgt^b*oAL)9A^xgntM
z$mGb-feD^Dw1{_P{PE?$%B>Cd^zy*Yt)ZRsT_-mMZ^G$2{D^Y~%KCy0%)RCB;am&f
zOX!JbZ?N>|>x}E~3Gi5}E%=TB&A*K_%YnVa22O4ZgcWvLACy45c?sidgK}OLtjFc6
z+#Qy3HgxlHv2vGsYmpt9bc(%Kxr2FEkN?WpumYIMog)+g!RP|mHI&dD|zrA^PtzK1jAVXBuN+*beov0JVnR0In
zajzA%1)hOGRUdZ+oN=co@j|I`Pg4()7ji+XMmdXOp{AsESvNu8DK~@~nY4s*HIO3NT*5?qW*55Oh(rq3
zj{xaqAf$z1@mcQj3w+s}LvQgaz{OX?XpSGupAN8-H-aMdxKX5yOUfY#_ye!*5uZY=
zAc_otX|REXxMI4v5akR?xrLQh4zK1;ZY{7mHczs)=9F@qI13aX_Lw`lu8fKi!o=MAoT^|HRGyiU68xYz0S
tox
diff --git a/src/tools/utils/plot_results.py b/src/tools/utils/plot_results.py
new file mode 100644
index 0000000..4b60cd1
--- /dev/null
+++ b/src/tools/utils/plot_results.py
@@ -0,0 +1,316 @@
+import arcpy
+import seaborn as sns
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+import os
+import re
+import cartopy.crs as ccrs
+from tools.utils.intersect_lines import *
+from matplotlib.colors import Normalize, TwoSlopeNorm
+from matplotlib.cm import ScalarMappable
+import matplotlib.lines as mlines
+from matplotlib.gridspec import GridSpec
+
+# Define the settings for the plots
+plt.style.use('classic')
+plt.rcParams['axes.grid'] = True
+plt.rcParams['grid.linestyle'] = '--'
+plt.rcParams['grid.linewidth'] = 0.5
+plt.rcParams['axes.prop_cycle'] = plt.cycler(color=['#5B9CFD', '#FECB31', '#bfbbd9', '#fa8174', '#81b1d2',
+ '#fdb462', '#b3de69', '#bc82bd', '#ccebc4', '#ffed6f'])
+
+class PlottingUtils():
+ def __init__(self, transects, shore_intersections):
+ """
+ Constructor method for initializing PlottingUtils class.
+
+ Parameters:
+ transects (arcpy.FeatureClass): Feature Class object for transects (Line geometries).
+ shore_intersections (arcpy.FeatureClass): Feature Class object for shoreline intersections (Point geometries).
+ """
+ self.transects = transects
+
+ # Set the directory where plots will be stored
+ aprx = arcpy.mp.ArcGISProject('CURRENT')
+ self.out_dir = os.path.join(aprx.homeFolder, 'Plots results')
+ if not os.path.exists(self.out_dir):
+ os.mkdir(self.out_dir)
+
+ # Convert Feature Classes to Pandas DataFrames
+ self.transects_df = self._feature_class_to_dataframe(transects)
+ self.shore_intersections_df = self._feature_class_to_dataframe(shore_intersections)
+ self.shore_intersections_df['date'] = pd.to_datetime(self.shore_intersections_df['date'])
+
+ # Create Shapely Object for transects geometries
+ self.transects_shapely = line_arcgis2shapely(feature=transects, id='transect_id')
+
+
+ def _feature_class_to_dataframe(self, feature_class):
+ """
+ Private method to convert an ArcGIS Feature Class to a Pandas DataFrame.
+
+ Parameters:
+ feature_class (arcpy.FeatureClass): Feature Class object.
+
+ Returns:
+ pd.DataFrame: Pandas DataFrame containing feature class data.
+ """
+ fields = [field.name for field in arcpy.ListFields(feature_class)]
+
+ return pd.DataFrame([row for row in arcpy.da.SearchCursor(feature_class, fields)], columns=fields)
+
+
+ def _get_UTM_projection(self):
+ """
+ Private method to get UTM projection from transects Feature Class.
+
+ Returns:
+ UTM_number (int): UTM number.
+ southern_hemisphere (bool): Whether or not the Feature Class is in the southern hemisphere.
+ """
+ # CRS string (e.g. 'WGS_1984_UTM_Zone_19N')
+ crs = arcpy.Describe(self.transects).SpatialReference.name
+
+ # Regex codes to extract the UTM number and the hemisphere
+ num_code = r'\b(\d+)[A-Za-z]?\b'
+ hemisphere_code = r'\b\d+([A-Za-z])\b'
+
+ UTM_number = int(re.findall(num_code, crs.split('_')[-1])[0])
+ hemisphere_UTM = re.findall(hemisphere_code, crs.split('_')[-1])[0]
+
+ if hemisphere_UTM == 'N':
+ southern_hemisphere = False
+ else:
+ southern_hemisphere = True
+
+ return UTM_number, southern_hemisphere
+
+
+ def _set_map_configuration(self, metric):
+ """
+ Private method to set the map configuration for plotting LRR, SCE and NSM.
+ That is, the colormap, the type of normalization scale and the type of colorbar according to the metric.
+
+ Parameters:
+ metric (string): Name of the feature to plot.
+
+ Returns:
+ cmap (matplotlib.colors.LinearSegmentedColormap): The colormap.
+ norm (matplotlib.colors.TwoSlopeNorm or matplotlib.colors.Normalize): The type of the normalization.
+ extend_cbar (string): The type of the colorbar according to cmap and norm.
+ """
+
+ metric_min, metric_max = self.transects_df[metric].describe()[['min', 'max']]
+
+ if metric_min < 0 and metric_max > 0:
+ cmap = plt.get_cmap('RdBu')
+ norm = TwoSlopeNorm(vmin=metric_min, vcenter=0, vmax=metric_max)
+ extend_cbar = 'both'
+ elif metric_min < 0 and metric_max < 0:
+ cmap = plt.get_cmap('Reds')
+ norm = Normalize(vmin=metric_min, vmax=0)
+ extend_cbar = 'min'
+ elif metric_min > 0 and metric_max > 0:
+ cmap = plt.get_cmap('Blues')
+ norm = Normalize(vmin=0, vmax=metric_max)
+ extend_cbar = 'max'
+
+ return cmap, norm, extend_cbar
+
+ # ===== FROM HERE, ALL THE FUNCTIONS TO CREATE THE PLOTS ARE DEFINED =====
+
+ def plot_spatial_evolution(self):
+ # Plot the distances between all shorelines and the baseline on each transect
+
+ fig, ax = plt.subplots(figsize=(12, 5))
+
+ # Plot all values as scatter
+ ax.scatter(self.shore_intersections_df['transect_id'],
+ self.shore_intersections_df['distance_from_base'],
+ alpha=.1, lw=0, zorder=1)
+
+ # Plot average width value for each transect
+ ax.plot(self.shore_intersections_df['transect_id'].unique(),
+ self.shore_intersections_df.groupby('transect_id')['distance_from_base'].mean(),
+ label='avg', color='#fa8174', lw=2, zorder=2)
+
+ # Plot mean +-2*std width value for each transect
+ ax.fill_between(self.shore_intersections_df['transect_id'].unique(),
+ self.shore_intersections_df.groupby('transect_id')['distance_from_base'].mean()+\
+ 2 * self.shore_intersections_df.groupby('transect_id')['distance_from_base'].std(),
+ self.shore_intersections_df.groupby('transect_id')['distance_from_base'].mean()-\
+ 2 * self.shore_intersections_df.groupby('transect_id')['distance_from_base'].std(),
+ color='#bfbbd9', alpha=.5, lw=0, label='avg\u00B12std', zorder=0)
+
+ # Plot settings
+ ax.set_xticks((np.arange(0, max(self.shore_intersections_df['transect_id']) + 2, 2)).tolist())
+ ax.set_xlabel('transect_id')
+ ax.set_ylabel('distance from baseline (m)')
+ ax.set_xlim([-1, max(self.shore_intersections_df['transect_id']) + 2])
+ plt.text(-0.05, 0.9, 'seaward', transform=plt.gca().transAxes, horizontalalignment='right', fontstyle='italic')
+ plt.text(-0.05, .1, 'landward', transform=plt.gca().transAxes, horizontalalignment='right', fontstyle='italic')
+ plt.grid(linestyle='--', alpha=0.3)
+ ax.legend()
+ ax.set_title('Spatial shoreline evolution (%.f - %.f)' % (self.shore_intersections_df['date'].min().year,
+ self.shore_intersections_df['date'].max().year))
+ fig.savefig(os.path.join(self.out_dir, 'Spatial shoreline evolution.png'), dpi=300, bbox_inches='tight')
+
+
+ def plot_time_series(self, transects2plot):
+ # Plot time series for selected transects
+
+ fig = plt.figure(figsize=(12, 2 * len(transects2plot)))
+ gs = GridSpec(len(transects2plot), 7, figure=fig)
+
+ for i, t in enumerate(transects2plot):
+ ax = fig.add_subplot(gs[i, :-1])
+
+ # Prepare the data to plot
+ data_transect = self.shore_intersections_df.loc[self.shore_intersections_df['transect_id'] == t, :]
+ data_transect.index = pd.to_datetime(data_transect['date'])
+ data_transect = data_transect.sort_index()
+ data_transect['Time'] = np.arange(len(data_transect.index))
+ X = data_transect.index.map(pd.Timestamp.toordinal) # features
+ y = data_transect.loc[:, 'distance_from_base'] # target
+
+ # Plot time series
+ #y.plot(ax=ax, color='#5B9CFD', linestyle='-', marker='o', markersize=2, label='shoreline positions')
+ ax.plot(X, y.values, linestyle='-', marker='o', markersize=2, label='shoreline positions')
+ sns.regplot(x=X, y=y, ci=95, scatter=False, label='linear regression fit', ax=ax)
+
+ # Plot settings
+ ax.set_ylabel('distance from\nbaseline (m)')
+ ax.locator_params(axis='y', nbins=4)
+ ax.set_title('transect ' + str(t))
+ ax.grid(alpha=0.3)
+
+ # Plot LRR error plot
+ ax2 = fig.add_subplot(gs[i, -1:])
+ lrr = self.transects_df.loc[self.transects_df['transect_id']==t, 'LRR']
+ lci = self.transects_df.loc[self.transects_df['transect_id']==t, ['LCI_low', 'LCI_upp']].to_numpy()[0]
+ ax2.errorbar(0, lrr, yerr=([abs(lci[0] - lrr.values[0])], [lci[1] - lrr.values[0]]),
+ fmt='or', markersize=8, capsize=5)
+ # Plot settings
+ ax2.set_xticklabels([])
+ ax2.locator_params(axis='y', nbins=4)
+ ax2.locator_params(axis='x', nbins=1)
+ ax2.grid(axis='y', alpha=0.3)
+ ax2.grid(axis='x', alpha=0)
+
+ if i == len(transects2plot) - 1:
+ # Time series plot
+ ax.set_xlabel('')
+ xticks = ax.get_xticks()
+ labels = [pd.Timestamp.fromordinal(int(label)).date().strftime("%m-%Y") for label in xticks]
+ ax.set_xticks(xticks)
+ ax.set_xticklabels(labels)
+ ax.legend(fontsize=8)
+
+ # LRR error plot
+ ax2.set_xlabel('LRR\u00B195%\n(m/year)')
+
+ else:
+ # Time series plot
+ ax.set_xlabel('')
+ ax.set_xticklabels([])
+
+ plt.subplots_adjust(hspace=0.4, wspace=1)
+
+ fig.savefig(os.path.join(self.out_dir, 'Time shoreline evolution.png'), bbox_inches='tight', dpi=300)
+
+
+ def plot_seasonality(self, transects2plot):
+ # Boxplot by months for selected transects
+
+ shore_month = self.shore_intersections_df.copy()
+ shore_month['month'] = shore_month['date'].dt.month
+
+ fig, ax = plt.subplots(len(transects2plot), 1, figsize=(10, 15), sharex=True)
+
+ for i, t in enumerate(transects2plot):
+ # Grab the data
+ data = shore_month.loc[shore_month['transect_id']==t, ['month', 'distance_from_base']]
+
+ # Median values Line plot
+ ax[i].plot(data.groupby('month')['distance_from_base'].median(),
+ color='#FECB31', marker='o', markeredgecolor=None, alpha=.7)
+
+ # Box plot
+ medianprops = dict(linestyle='-', linewidth=2, color='#FECB31')
+ whiskerprops = dict(linestyle='-', color='#5B9CFD')
+ data.boxplot(column='distance_from_base', by='month', ax=ax[i], medianprops=medianprops, whiskerprops=whiskerprops)
+
+ # Plot settings
+ ax[i].set_xlabel('')
+ ax[i].set_ylabel('distance from\nbaseline (m)')
+ ax[i].locator_params(axis='y', nbins=5)
+ ax[i].set_title('transect ' + str(t))
+ ax[i].grid(linestyle='--', alpha=0.3)
+ ax[i].grid(axis='x', alpha=0)
+
+ if i == len(transects2plot) - 1:
+ ax[i].set_xticklabels(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] * len(transects2plot))
+
+ fig.suptitle(None)
+ plt.subplots_adjust(hspace=0.2)
+ fig.savefig(os.path.join(self.out_dir, 'Shoreline evolution seasonality.png'), dpi=300, bbox_inches='tight')
+
+
+ def plot_map(self, metric):
+ # Plot a map of the selected metric (LRR, SCE or NSM)
+
+ # Set the cmap, norm and the type of cbar of the plot
+ cmap, norm, extend_cbar = self._set_map_configuration(metric)
+
+ # Set projection parameter
+ UTM_number, southern_hemisphere = self._get_UTM_projection()
+ proj = ccrs.UTM(UTM_number, southern_hemisphere=southern_hemisphere)
+
+ # Create a subplot with UTM projection
+ fig, ax = plt.subplots(layout='compressed', subplot_kw={'projection':proj})
+
+ # Iterate through transects and plot lines
+ for i, t in self.transects_shapely.items():
+
+ # Check if Pvalue is less than 0.05 for significance
+ if self.transects_df.loc[self.transects_df['transect_id'] == i, 'Pvalue'].values <= 0.05:
+ color = cmap(norm(self.transects_df.loc[self.transects_df['transect_id'] == i, metric]))
+ ls = '-'
+ else:
+ color = 'gray'
+ ls = '--'
+
+ # Plot the transect line
+ ax.plot(*t.xy,
+ color=color,
+ transform=proj,
+ lw=3,
+ ls=ls)
+
+ # Create a legend entry for non-significant transects
+ legend_entry = mlines.Line2D([], [], color='gray', lw=2, ls='--', label='Non-significant transect')
+
+ # Customize gridlines, ticks, and appearance
+ ax.gridlines(crs=proj, linewidth=1, color='black', alpha=0, linestyle="--")
+ ax.set_xticks(ax.get_xticks(), crs=proj)
+ ax.set_yticks(ax.get_yticks(), crs=proj)
+ ax.grid(alpha=.3)
+
+ # Add colorbar and set title
+ if self.transects_df['Pvalue'].min() <= 0.05:
+ fig.colorbar(ScalarMappable(norm=norm, cmap=cmap), extend=extend_cbar, ax=ax)
+ if metric == 'LRR':
+ ax.set_title('Linear Regression Rate, LRR (m/year)', y=1.05)
+ elif metric == 'SCE':
+ ax.set_title('Shoreline Change Envelope, SCE (m)', y=1.05)
+ elif metric == 'NSM':
+ ax.set_title('Net Shoreline Movement, NSM (m)', y=1.05)
+
+ # Set limits, labels, legend, and save the figure
+ lons = [x for t in self.transects_shapely.values() for x in t.xy[0]] # Extract the longitudes for plotting purposes (Sometimes the plot is centered to the west and a blank space is left to the east of the study area)
+ ax.set_xlim([ax.get_xlim()[0], max(lons)])
+ ax.set_xlabel('Eastings (m)')
+ ax.set_ylabel('Northings (m)')
+ ax.legend(handles=[legend_entry], fontsize='small')
+ fig.savefig(os.path.join(self.out_dir, '{0}_transects.png'.format(metric)), dpi=300, bbox_inches='tight')
diff --git a/src/tools/utils/transect_processor.py b/src/tools/utils/transect_processor.py
index d8de6da..ae487d1 100644
--- a/src/tools/utils/transect_processor.py
+++ b/src/tools/utils/transect_processor.py
@@ -17,6 +17,7 @@ def invert_angles(self):
# Detect the first transects that are inverted (if there are more than one change). To do so, find a difference angle around ~180ยบ (it has been selected a range of 180 +-50).
start_change_transects = self.df[(self.df['diffBear'].abs() >= 130) & (self.df['diffBear'].abs() < 230)]['transect_id'].to_list()
+ # Empty list of the transects that need to be inverted
transects2correct = []
for i, _ in enumerate(start_change_transects):
if (i + 1) % 2 != 0: # Odd number
@@ -30,7 +31,7 @@ def invert_angles(self):
pass
self.df['Angle'] = 0
- self.df.loc[self.df['transect_id'].isin(transects2correct), 'Angle'] = 180 # Rotate 180 degrees the bearing anle
+ self.df.loc[self.df['transect_id'].isin(transects2correct), 'Angle'] = 180 # Rotate 180 degrees the bearing angle
def classify_transects(self):
# Classify transects with large differences using the corrFactor value. In addition, try not to take into account the differences in the 360-0 sector.
@@ -49,11 +50,9 @@ def __init__(self, df, fclass):
# Initialize the class by adding an angle field with the values calculated above
arcpy.management.AddField(fclass, 'Angle', 'DOUBLE')
- count = 0
with arcpy.da.UpdateCursor(fclass, 'Angle') as cursor:
- for row in cursor:
- cursor.updateRow([df.loc[count, 'Angle']])
- count += 1
+ for i, row in enumerate(cursor):
+ cursor.updateRow([df.loc[i, 'Angle']])
# Rebuild each polyine with rotated vertices
with arcpy.da.UpdateCursor(fclass, ['SHAPE@', 'Angle']) as cursor: