Skip to content

Commit

Permalink
New release
Browse files Browse the repository at this point in the history
Among other things, a new functionality has been added related to the plotting of results. More info at release description.
  • Loading branch information
AlbertGallegoJimenez committed Jan 10, 2024
1 parent ec7745e commit de3ddb4
Show file tree
Hide file tree
Showing 12 changed files with 482 additions and 24 deletions.
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@ This tool is developed as part of a [Python Toolbox](https://pro.arcgis.com/en/p
## Getting Started

### Prerequisites
The following is a list of the programs and libraries used in the tool, with their respective versions:

* ArcGIS Pro - ```arcpy``` (version 3.1)
* ```pandas``` (version 1.4)
* ```numpy``` (version 1.20)
* ```shapely``` (version 2.0)
* ```statsmodels``` (version 0.13)
Check that you have installed all the required libraries used in the toolbox. All packages with their tested versions are listed in [requirements.txt](https://github.com/AlbertGallegoJimenez/shoreline-evolution-tool/tree/main/requirements.txt). Note that when working with a cloned version of the ArcGIS ```anaconda``` environment there are already pre-installed libraries, those that are not and need to be installed manually are the following:

* ```shapely```
* ```statsmodels``` (from ArcGIS version 3.2 this package is included in the base ArcGIS ```anaconda``` environment)
* ```cartopy```

In terms of data, this tool relies on the use of the following two files:
* <ins>**Baseline**</ins> (Vector - Polyline). This is the reference line used to assess the evolution of the coastal stretch. It can be digitized manually by the user with the help of a background orthophoto, it is recommended to place the baseline **inland**. The baseline must capture the general orientation of the coast.
Expand All @@ -51,8 +50,8 @@ In terms of data, this tool relies on the use of the following two files:

### Installation

0. Make sure you have cloned the base ArcGIS' ```anaconda``` environment so you can install more packages. More info [here](https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/clone-an-environment.htm).
1. Install both ```shapely``` and ```statsmodels``` packages.
0. Make sure you have cloned the base ArcGIS ```anaconda``` environment so you can install more packages. More info [here](https://pro.arcgis.com/en/pro-app/latest/arcpy/get-started/clone-an-environment.htm).
1. Install ```shapely```, ```statsmodels``` and ```cartopy``` packages if you do not have it installed yet.
2. Download the content in the [src](https://github.com/AlbertGallegoJimenez/shoreline-evolution-tool/tree/main/src) folder.
3. Open the Catalog Pane in ArcGIS Pro and open the downloaded Toolbox (.pyt) to see the tools.
<div align="center">
Expand Down
11 changes: 11 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Name Version
arcgispro (software) 3.1
arcpy 3.1
cartopy 0.21.1
matplotlib 3.6.0
numpy 1.20.1
pandas 1.4.4
regex 2022.7.9
seaborn 0.12.1
shapely 2.0.1
statsmodels 0.13.5
4 changes: 2 additions & 2 deletions src/tools/correctTransects.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def execute(self, parameters, messages):

# Add Bearing attribute
arcpy.management.CalculateGeometryAttributes(transectsFeature, "Bearing LINE_BEARING")

cursor = arcpy.da.SearchCursor(transectsFeature, ["transect_id", "Bearing"])
df = pd.DataFrame(data=[row for row in cursor], columns=["transect_id", "Bearing"])

Expand All @@ -87,7 +86,7 @@ def execute(self, parameters, messages):
# Make the changes in the feature class
RotateFeatures(df, transectsFeature)

# Recalulate the bearing with the transects inverted
# Recalculate the bearing with the transects inverted
arcpy.management.CalculateGeometryAttributes(transectsFeature, "Bearing LINE_BEARING")
cursor = arcpy.da.SearchCursor(transectsFeature, ["transect_id", "Bearing"])
df = pd.DataFrame(data=[row for row in cursor], columns=["transect_id", "Bearing"])
Expand All @@ -111,6 +110,7 @@ def execute(self, parameters, messages):
def postExecute(self, parameters):
"""This method takes place after outputs are processed and
added to the display."""
# Update the labels of the transect feature
aprx = arcpy.mp.ArcGISProject('CURRENT')
aprxMap = aprx.activeMap
transectsFeature = aprxMap.listLayers(os.path.basename(parameters[0].valueAsText))[0]
Expand Down
1 change: 1 addition & 0 deletions src/tools/generateTransects.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def execute(self, parameters, messages):
def postExecute(self, parameters):
"""This method takes place after outputs are processed and
added to the display."""
# Update the labels of the transect feature
aprx = arcpy.mp.ArcGISProject('CURRENT')
aprxMap = aprx.activeMap
transectsFeature = aprxMap.listLayers(os.path.basename(parameters[0].valueAsText))[0]
Expand Down
28 changes: 19 additions & 9 deletions src/tools/performAnalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,9 @@ def execute(self, parameters, messages):
for field in metrics_fields:
arcpy.management.AddField(transectsFeature, field, "DOUBLE")

count = 0
with arcpy.da.UpdateCursor(transectsFeature, metrics_fields) as cursor:
for row in cursor:
cursor.updateRow(shore_metrics.loc[count, metrics_fields].tolist())
count += 1
for i, _ in enumerate(cursor):
cursor.updateRow(shore_metrics.loc[i, metrics_fields].tolist())

return

Expand All @@ -102,8 +100,6 @@ def postExecute(self, parameters):

# Get the current symbology settings of the layer
sym = transectsLayerObj.symbology
# Get the layer's CIM definition
cim = transectsLayerObj.getDefinition('V3')

# Set the renderer to Graduated Colors
sym.updateRenderer('GraduatedColorsRenderer')
Expand All @@ -113,7 +109,6 @@ def postExecute(self, parameters):

# Specify the field used for classification
sym.renderer.classificationField = 'LRR'
cim.renderer.heading = 'LRR (m/year)'

# Set the number of class breaks
sym.renderer.breakCount = 6
Expand All @@ -125,7 +120,7 @@ def postExecute(self, parameters):
labels = ["-50.0 - -4.0", "-4.0 - -2.0", "-2.0 - 0.0", "0.0 - 2.0", "2.0 - 4.0", "4.0 - 50.0"]

# Define sizes for each class
sizes = [4, 2, 1, 1, 2, 4]
sizes = [6, 3, 1.5, 1.5, 3, 6]

# Update values for each class
for i, brk in enumerate(sym.renderer.classBreaks):
Expand All @@ -135,6 +130,21 @@ def postExecute(self, parameters):

# Apply the updated symbology settings to the layer
transectsLayerObj.symbology = sym
transectsLayerObj.setDefinition(cim)

# Get the layer's CIM definition
cim = transectsLayerObj.getDefinition('V3')

# Set the label for the symbolised field
cim.renderer.heading = 'LRR (m/year)'

# Exclude non-significant transects and apply unique symbology
cim.renderer.useExclusionSymbol = True
cim.renderer.exclusionClause = 'Pvalue > 0.05'
cim.renderer.exclusionLabel = 'Non-significant transect'
cim.renderer.exclusionSymbol.symbol.symbolLayers[0].color.values = [130, 130, 130, 100]
cim.renderer.exclusionSymbol.symbol.symbolLayers[0].width = 1.5

# Update the CIM
transectsLayerObj.setDefinition(cim)

return
122 changes: 122 additions & 0 deletions src/tools/plotResults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import arcpy
from tools.utils.plot_results import PlottingUtils

class PlotResults(object):
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "5. Plot The Analysis Results"
self.description = ""
self.canRunInBackground = False

def getParameterInfo(self):
"""Define parameter definitions"""

shoreline_param = arcpy.Parameter(
displayName="Input Shorelines Intersection Points Feature",
name="shore_features",
datatype="GPFeatureLayer",
parameterType="Required",
direction="Input")
shoreline_param.filter.list = ["Point"]

transects_param = arcpy.Parameter(
displayName="Input Transects Feature",
name="transects_features",
datatype="GPFeatureLayer",
parameterType="Required",
direction="Input")
transects_param.filter.list = ["Polyline"]

transects_ID_2plot_param = arcpy.Parameter(
displayName="Input Transects ID To Plot",
name="transects_ID_2plot",
datatype="GPValueTable",
parameterType="Required",
direction="Input")
transects_ID_2plot_param.columns = [['GPLong', 'Transects ID']]

parameters = [shoreline_param, transects_param, transects_ID_2plot_param]

return parameters

def isLicensed(self):
"""Set whether tool is licensed to execute."""
return True

def updateParameters(self, parameters):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
return

def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
parameters[2].setWarningMessage(
"Check significance before selecting transects (Pvalue < 0.05).")
return

def execute(self, parameters, messages):
"""The source code of the tool."""
shoreFeatures = parameters[0].valueAsText
transectsFeature = parameters[1].valueAsText
transectsID_2plot = parameters[2].value
transectsID_2plot = [id[0] for id in transectsID_2plot] # As the parameter value is passed as a list of lists, instead of a list of integers

# Check for errors in the transect IDs selected
self._check_transects_id(transectsID_2plot, transectsFeature)

# Initialize the class
plotter = PlottingUtils(transects=transectsFeature,
shore_intersections=shoreFeatures)

# Plot the spatial evolution
plotter.plot_spatial_evolution()

# Plot the time series for the transects selected
plotter.plot_time_series(transects2plot=transectsID_2plot)

# Plot the seasonality for the transects selected. Set a minimum of 2 years of data to plot the seasonality.
if plotter.shore_intersections_df['date'].dt.year.max() - plotter.shore_intersections_df['date'].dt.year.min() >= 2:
plotter.plot_seasonality(transects2plot=transectsID_2plot)

# Plot the LRR map
plotter.plot_map('LRR')

# Plot the SCE map
plotter.plot_map('SCE')

# Plot the NSM map
plotter.plot_map('NSM')

return

def postExecute(self, parameters):
"""This method takes place after outputs are processed and
added to the display."""
return

def _check_transects_id(self, transectsID_2plot, transectsFeature):
"""This private method checks that all transects
passed by the user are valid."""

# Check that all IDs are numerical. This code may be unnecessary since the parameter is defined as 'GPLong' and this means that ArcGIS will only accept numerical values.
if not all(isinstance(id, (int, float)) for id in transectsID_2plot):
arcpy.AddError('Invalid transect ID, check that all IDs are numeric.')
raise Exception('Invalid transect ID, check that all IDs are numeric.')

# Check for duplicates
if len(transectsID_2plot) != len(set(transectsID_2plot)):
arcpy.AddError('Some transects are repeated.')
raise Exception('Some transects are repeated.')

# Check if the user only has selected one transect
if len(transectsID_2plot) == 1:
arcpy.AddError('Please, select more than one transect to plot.')
raise Exception('Please, select more than one transect to plot.')

# Check for ID out of range
list_transect_id = [row[0] for row in arcpy.da.SearchCursor(transectsFeature, ['transect_id'])]
if set(transectsID_2plot) - set(list_transect_id):
arcpy.AddError('Invalid transect ID, check that all IDs are within the valid range.')
raise Exception('Invalid transect ID, check that all IDs are within the valid range.')
Binary file modified src/tools/utils/__pycache__/intersect_lines.cpython-39.pyc
Binary file not shown.
Binary file not shown.
Binary file modified src/tools/utils/__pycache__/shoreline_evolution.cpython-39.pyc
Binary file not shown.
Binary file modified src/tools/utils/__pycache__/transect_processor.cpython-39.pyc
Binary file not shown.
Loading

0 comments on commit de3ddb4

Please sign in to comment.