Skip to content

Commit

Permalink
Documentation & README update
Browse files Browse the repository at this point in the history
  • Loading branch information
eef-g committed Nov 18, 2023
1 parent 139ffd5 commit 6d994b9
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/


data/parquets/*
22 changes: 14 additions & 8 deletions GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
from graph import HydroGraph

def create_gui():
# Create a HydroGraph class to use all of its functions
graph_backend = HydroGraph()

# Create the main window
root = tk.Tk()
root.title("River Flow Predictions")
root.title("FlowForecast")
root.iconbitmap("data/eef-g.ico")

# Create a Figure and a Canvas to draw the graph
Expand All @@ -34,8 +35,12 @@ def validate_input(input):
# Add a date selector
date_selector = DateEntry(root)
date_selector.pack()


# Add a button
def collect_info():
if not os.path.exists("data/parquets"):
os.makedirs("data/parquets")

selcted_date = date_selector.get_date().strftime("%Y-%m-%d")
# selected_date = selected_date.strftime("%Y-%m-%d")
selected_sensor = textbox.get()
Expand All @@ -50,21 +55,22 @@ def collect_info():
canvas.figure = fig
canvas.draw()


button = ttk.Button(root, text="Get Data", command=collect_info)
button.pack()

# Add a second button that clears the cached data
def clear_cache():
cache_folder = "data"
parquet_subfolder = f"{cache_folder}/parquets"

for file in os.listdir(parquet_subfolder):
file_path = os.path.join(parquet_subfolder, file)
os.remove(file_path)

# Add a button
button = ttk.Button(root, text="Get Data", command=collect_info)
button.pack()


# Add a second button that clears the cached data
cache_button = ttk.Button(root, text="Clear Cache", command=clear_cache)
cache_button.pack()

# Start the main loop
# Start the application loop
tk.mainloop()
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
# Streamflow-Predictions
A python application that allows the user to predict waterflow in a given area
<span>
<h1 align="center"> FlowForecast </h1>
<h3 align="center"> A python application to predict river flow </h3>
</span>

<span>
<h3 align="center">Libraries used:</h3>
<span>
<p align="center">
<a href="https://hydrofunctions.readthedocs.io/en/master/" target="_blank" rel="noreferrer"> <img src="https://hydrofunctions.readthedocs.io/en/master/_static/hf-logo.svg" alt="CHydrofunctions" width="40" height="40"/> </a>
<a href="https://pandas.pydata.org/" target="_blank" rel="noreferrer"> <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Pandas_mark.svg/1200px-Pandas_mark.svg.png" alt="Pandas" width="40" height="40"/> </a>
<a href="https://numpy.org/" target="_blank" rel="noreferrer"> <img src="https://numpy.org/images/logo.svg" alt="Numpy" width="40" height="40"/> </a>
<a href="https://matplotlib.org/" target="_blank" rel="noreferrer"> <img src="https://upload.wikimedia.org/wikipedia/commons/0/01/Created_with_Matplotlib-logo.svg" alt="Matplotlib" width="40" height="40"/> </a>
</p>
</span>
</span>
</br>
</br>
</br>
</br>

<h1 align="center"> Features: </h1>
<ul>
<li>
<p>Real-time data collection thanks to the hydrofunctions library!</p>
</li>
<li>
<p>Data caching to reduce on load times for repeated queries!</p>
</li>
<li>
<p>Saving your graphs as images to store them for later!<p>
</li>
<li>
<p>A quick-and-easy setup that only requires one python command!
</li>
</ul>
</br>
</br>
</br>

<h1 align="center"> Example Graph: </h1>
<p align="center">
<img src="README_data/plot.png" alt="Example graph">
</p>
</br>
</br>
</br>
</br>
<h1 align="center"> Planned Features:</h1>
<ul>
<li>
<p> Cleaner & more intuitive UI </p>
</li>
<li>
<p> Loading text and faster data fetching </p>
</li>
</ul>
Binary file added README_data/plot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 48 additions & 12 deletions graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@
from datetime import datetime, timedelta

class HydroGraph:
"""
This is the custom class that collects, parses, and plots data from a hydro sensor
"""
def __init__(self):
self.ResetVariables()

def ResetVariables(self):
"""
Reset all class variables to allow for different sites and anchor dates to be passed through
"""
# Self string variables
self.sensor_id = ''
self.anchor_date = ''
Expand Down Expand Up @@ -43,20 +49,27 @@ def ResetVariables(self):
self.graph_finished = False

def SetFullInfo(self, date, id):
"""
Sets all of the information needed for the class at the same time
"""
self.ResetVariables()
self.SetSensorId(id)
self.SetAnchorDate(date)

def SetSensorId(self, id):
""" Set the class's sensor_id variable to the given id"""
self.sensor_id = id

def SetAnchorDate(self, date):
""" Set the class's anchor_date to the given date"""
self.anchor_date = date
self.current_year = date[:4]
self.GetSensorName()
self.CreatePlot()

def CreatePlot(self):
"""Plot all of the data stored in the class and assign it to the Figure in the class
[DOES NOT DISPLAY THE PLOT]"""
# Collect all the needed data
self.sensor_df = self.GetSensorDf(self.anchor_date, True) # Get the modern data
self.GetHistoricDfs() # Get the historic data dfs
Expand All @@ -68,11 +81,7 @@ def CreatePlot(self):
self.ConvertDfs() # Converts all the dfs to arrays and stores them in the class

# Split modern x and y vals for later
modern_x = []
modern_y = []
for point in self.modern_data:
modern_x.append(point[0])
modern_y.append(point[1])
modern_x, modern_y = zip(*self.modern_data)

# Calculate the volume & flow
volume = self.CalculateVolume(modern_y)
Expand Down Expand Up @@ -117,7 +126,6 @@ def CreatePlot(self):
self.ax.fill_between( avg_x, avg_high_y, avg_low_y, color='Gray', alpha=0.5)

# Plot the anchor date
# today_obj = datetime.strptime(modern_x[-1], '%m-%d')
self.ax.axvline(modern_x[-1], color='r', linestyle='--', label=f'{datetime.strftime(modern_x[-1], "%b %m %H:%M")}') # Add vertical line

# Plot the linear regression line
Expand All @@ -131,12 +139,13 @@ def CreatePlot(self):
self.fig.autofmt_xdate()
self.ax.legend()

# self.fig.show()
# Save the figure to disk & change the graph_finished flag for the GUI
self.fig.savefig('plot.png')
self.graph_finished = True


def GetPlot(self):
"""Returns the matplotlib Figure object"""
return self.fig


Expand All @@ -145,14 +154,18 @@ def GetHistoricDfs(self):
overall_data = []
for i in range(int(self.current_year) - 9, int(self.current_year)):
today_past = str(i) + self.anchor_date[4:]
# Request the data from year i and add it to the array
overall_data.append(self.GetSensorDf(today_past, False))
self.historic_dfs = overall_data
self.historic_dfs = overall_data # Set class variable to the array of historic dataframes


def GetSensorDf(self, anchor, today_max=False):
"""Grabs data from the class's sensor_id variable and gets data over a 3 week period"""
# Calculate the start date as 2 weeks in the past from today
past_date = self.GetPastDate(anchor)
future_date = self.GetFutureDate(anchor)

# Check if the given anchor date is the current date
if not today_max:
request = hf.NWIS(self.sensor_id, 'iv', past_date, future_date, file=f'data/parquets/{self.sensor_id}-{anchor}.parquet', verbose=False)
else:
Expand All @@ -164,33 +177,43 @@ def GetSensorDf(self, anchor, today_max=False):


def GetSensorName(self):
"""Uses the class's sensor_id variable to get the name of where the sensor is"""
past_date = self.GetPastDate(self.anchor_date)
request = hf.NWIS(self.sensor_id, 'iv', past_date, self.anchor_date, file=f'data/parquets/{self.sensor_id}-{self.anchor_date}.parquet', verbose=False)
# From the request, parse the header and get the name of the sensor
sensor_caps = str(request)[str(request).index(' '):str(request).index('\n')]
sensor_title = sensor_caps.title()
# Set the sensor name to the formatted string slices
self.sensor_name = sensor_title[:-2] + sensor_caps[-2:]


def GetPastDate(self, date_str):
"""Returns a string two weeks in the past from the given date"""
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
past_date = date_obj - timedelta(weeks=2)
return past_date.strftime('%Y-%m-%d')


def GetFutureDate(self, date_str):
"""Returns a string one week in the future from the given date"""
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
future_date = date_obj + timedelta(weeks=1)
return future_date.strftime('%Y-%m-%d')


def GetLowestDf(self):
"""Finds the DataFrame that contains the lowest point from the historical data"""
# Set lowest to the current year's lowest point
lowest = min(self.sensor_df.loc[:, list(self.sensor_df)[0]])
lowest_index = -1
# Go thru each historic dataframe and look for a lower point
for i in range(len(self.historic_dfs)):
historic_lowest = min(self.historic_dfs[i].loc[:, list(self.historic_dfs[i])[0]])
# If year i has a lower point than what is stored, update the stored lowest point
if historic_lowest < lowest:
lowest = historic_lowest
lowest_index = i
# Check if there was a lower point than current year's lowest & update values accordingly
if lowest_index != -1:
self.lowest_df = self.historic_dfs[lowest_index]
self.lowest_year = str(int(self.current_year) - lowest_index + 1)
Expand All @@ -200,13 +223,18 @@ def GetLowestDf(self):


def GetHighestDf(self):
"""Finds the DataFrame that contains the highest point from the historical data"""
# Set highest to the current year's highest point
highest = max(self.sensor_df.loc[:, list(self.sensor_df)[0]])
highest_index = -1
# Go thru each historic dataframe and look for a higher point
for i in range(len(self.historic_dfs)):
historic_highest = max(self.historic_dfs[i].loc[:, list(self.historic_dfs[i])[0]])
# If year i has a higher point than what is stored, update the stored highest point
if historic_highest > highest:
highest = historic_highest
highest_index = i
# Check if there was a higher point than the current year's highest & update values accordingly
if highest_index != -1:
self.highest_df = self.historic_dfs[highest_index]
self.highest_year = str(int(self.current_year) - highest_index + 1)
Expand All @@ -216,12 +244,18 @@ def GetHighestDf(self):


def GetAverageFromData(self):
"""Creates a dataframe that contains the average for each timestamp from all historic data"""
# Set aside a list for where we will store the y-values
disc_values = []
# Go through each row of the historic dataframes
for i in range(len(self.historic_dfs[0])):
# Set aside list to store the current row at each historic year
curr_time_data = []
# Go thru each year and add the year j's value at row i
for j in range(len(self.historic_dfs)):
data_lump = self.historic_dfs[j].loc[:, list(self.historic_dfs[j])[0]]
curr_time_data.append(data_lump[i])
# Calculate the average of the row and add it to the overall average y-values
avg = sum(curr_time_data) / len(curr_time_data)
disc_values.append(avg)

Expand All @@ -232,13 +266,15 @@ def GetAverageFromData(self):


def ConvertDfs(self):
"""Converts each class dataframe into a zipped list for graphing"""
self.modern_data = self.DfToArray(self.sensor_df)
self.lowest_data = self.DfToArray(self.lowest_df)
self.highest_data = self.DfToArray(self.highest_df)
self.average_data = self.DfToArray(self.average_df)


def DfToArray(self, df):
"""Converts the given dataframe, df, into a zipped list"""
columns = list(df)
y_vals = df.loc[:, columns[0]]
raw_dates = [str(date) for date in df.index]
Expand All @@ -247,6 +283,7 @@ def DfToArray(self, df):


def CalculateVolume(self, y_vals):
"""Calculates the river's total volume from the given y_values. Assumes that data is taken in CFS every 15 minutes."""
# Time interval in seconds (15 minutes = 900 seconds)
delta_t = 15 * 60
# Calculate total volume in cubic feet
Expand All @@ -257,6 +294,7 @@ def CalculateVolume(self, y_vals):


def CalculateFlow(self, y_vals):
"""Calculates the river's flow from the given y_values. Assumes that data is taken in CFS every 15 minutes."""
if len(y_vals) < 2:
return None
last_val = y_vals[-1]
Expand All @@ -270,7 +308,5 @@ def CalculateFlow(self, y_vals):
if __name__ == "__main__":
trinity_id = '11527000'
today = '2023-06-05'
# graph = HydroGraph()
# graph.SetFullInfo(today, trinity_id)
nwis_dict = hf.extract_nwis_df()
print(nwis_dict)
graph = HydroGraph()
graph.SetFullInfo(today, trinity_id)
4 changes: 3 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
REQUIRED_PACKAGES = ['hydrofunctions', 'tkcalendar','matplotlib',
'pandas', 'numpy']

# Check and make sure all of the required packages are installed on the computer & install any uninstalled packages
for package in REQUIRED_PACKAGES:
try:
dist = distribution(package)
except:
subprocess.call(['pip', 'install', package])

# Now import the files that are used in the project to avoid any missing dependencies
import GUI
import warnings

if __name__ == "__main__":
warnings.simplefilter(action='ignore', category=FutureWarning)
warnings.simplefilter(action='ignore', category=FutureWarning) # Mute the warning about sorting by keys
GUI.create_gui()

0 comments on commit 6d994b9

Please sign in to comment.