Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 68 additions & 22 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import streamlit as st
from datetime import date, datetime
from datetime import date
from streamlit_js_eval import streamlit_js_eval # For geolocation
from src.data_acquisition import get_solunar_data
from src.data_processing import process_solunar_data
Expand Down Expand Up @@ -33,7 +33,10 @@
st.sidebar.header("Input Parameters")

# Checkbox to use current location
use_current_location = st.sidebar.checkbox("Use my current location", value=st.session_state.use_current_location)
use_current_location = st.sidebar.checkbox(
"Use my current location",
value=st.session_state.use_current_location
)
st.session_state.use_current_location = use_current_location

# Button to fetch data
Expand Down Expand Up @@ -65,28 +68,53 @@
st.session_state.latitude = loc["latitude"]
st.session_state.longitude = loc["longitude"]
st.session_state.loc = loc
st.sidebar.success(f"Location acquired: ({st.session_state.latitude:.6f}, {st.session_state.longitude:.6f})")
st.sidebar.success(
f"Location acquired: "
f"({st.session_state.latitude:.6f}, "
f"{st.session_state.longitude:.6f})"
)
else:
st.sidebar.error("Unable to retrieve location. Please allow location access.")
st.sidebar.error(
"Unable to retrieve location. "
"Please allow location access."
)
else:
st.sidebar.warning("Waiting for location... Make sure to allow location access.")
st.sidebar.warning(
"Waiting for location... "
"Make sure to allow location access."
)
else:
# Manual input
st.session_state.latitude = st.sidebar.number_input("Latitude", value=st.session_state.latitude, format="%.6f")
st.session_state.longitude = st.sidebar.number_input("Longitude", value=st.session_state.longitude, format="%.6f")
st.session_state.latitude = st.sidebar.number_input(
"Latitude", value=st.session_state.latitude, format="%.6f"
)
st.session_state.longitude = st.sidebar.number_input(
"Longitude", value=st.session_state.longitude, format="%.6f"
)

selected_date = st.sidebar.date_input("Date", value=date.today())

if fetch_data:
with st.spinner('Fetching data...'):
raw_data = get_solunar_data(st.session_state.latitude, st.session_state.longitude, selected_date)
raw_data = get_solunar_data(
st.session_state.latitude,
st.session_state.longitude,
selected_date
)
if raw_data is None:
st.error("Failed to retrieve data. Please try again later.")
else:
date_str = selected_date.strftime('%Y-%m-%d') # Convert date to string
st.session_state.solunar_data = process_solunar_data(raw_data, date_str)
major_times, minor_times = calculate_major_minor_times(st.session_state.solunar_data)
st.session_state.recommendations = generate_recommendations(major_times, minor_times)
# Convert date to string
date_str = selected_date.strftime('%Y-%m-%d')
st.session_state.solunar_data = process_solunar_data(
raw_data, date_str
)
major_times, minor_times = calculate_major_minor_times(
st.session_state.solunar_data
)
st.session_state.recommendations = generate_recommendations(
major_times, minor_times
)
st.session_state.data_fetched = True

# Display Results if Data Has Been Fetched
Expand All @@ -95,22 +123,40 @@

# Display Recommendations
st.header("Recommended Fishing Times")
for rec in st.session_state.recommendations:
start_time = rec['start'].strftime('%I:%M %p')
end_time = rec['end'].strftime('%I:%M %p')
st.write(f"**{rec['type']} Period:** {start_time} - {end_time}")
if st.session_state.recommendations:
for rec in st.session_state.recommendations:
start_time = rec['start'].strftime('%I:%M %p')
end_time = rec['end'].strftime('%I:%M %p')
st.write(f"**{rec['type']} Period:** {start_time} - {end_time}")
else:
st.warning(
"No major or minor times could be calculated. "
"This may be due to missing moonrise/moonset data."
)

# Display Additional Information
st.header("Additional Information")
st.write(f"**Location:** {st.session_state.latitude:.6f}, {st.session_state.longitude:.6f}")
st.write(f"**Sunrise:** {st.session_state.solunar_data['sunrise'].strftime('%I:%M %p')}")
st.write(f"**Sunset:** {st.session_state.solunar_data['sunset'].strftime('%I:%M %p')}")
st.write(f"**Moon Phase:** {st.session_state.solunar_data['moon_phase']}")
st.write(
f"**Location:** {st.session_state.latitude:.6f}, "
f"{st.session_state.longitude:.6f}"
)
sunrise_str = st.session_state.solunar_data['sunrise'].strftime('%I:%M %p')
st.write(f"**Sunrise:** {sunrise_str}")
sunset_str = st.session_state.solunar_data['sunset'].strftime('%I:%M %p')
st.write(f"**Sunset:** {sunset_str}")
moon_phase = st.session_state.solunar_data['moon_phase']
st.write(f"**Moon Phase:** {moon_phase}")

# Display Map
st.header("Map")
m = folium.Map(location=[st.session_state.latitude, st.session_state.longitude], zoom_start=12)
folium.Marker([st.session_state.latitude, st.session_state.longitude], tooltip="Your Location").add_to(m)
m = folium.Map(
location=[st.session_state.latitude, st.session_state.longitude],
zoom_start=12
)
folium.Marker(
[st.session_state.latitude, st.session_state.longitude],
tooltip="Your Location"
).add_to(m)
st_folium(m, width=700, height=500)
else:
st.info("Please enter parameters and click 'Get Fishing Times'")
22 changes: 17 additions & 5 deletions src/data_acquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import requests
import streamlit as st


def get_api_key():
# First, try to get the API_KEY from st.secrets
try:
Expand All @@ -27,20 +28,28 @@ def get_api_key():
pass

# If all else fails, display an error and stop the app
st.error("API_KEY not found. Please set it in Streamlit Secrets, as an environment variable, or in config.toml.")
st.error(
"API_KEY not found. Please set it in Streamlit Secrets, "
"as an environment variable, or in config.toml."
)
st.stop()


API_KEY = get_api_key()


def get_solunar_data(lat, lon, date):
url = 'https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/'
date_str = date.strftime('%Y-%m-%d')
base_url = (
'https://weather.visualcrossing.com/'
'VisualCrossingWebServices/rest/services/timeline/'
)
url = f"{base_url}{lat},{lon}/{date.strftime('%Y-%m-%d')}"
params = {
'key': API_KEY,
'include': 'hours',
'elements': 'datetime,sunrise,sunset,moonphase,moonrise,moonset',
}
response = requests.get(f"{url}{lat},{lon}/{date_str}", params=params)
response = requests.get(url, params=params)
if response.status_code == 200:
try:
data = response.json()
Expand All @@ -50,6 +59,9 @@ def get_solunar_data(lat, lon, date):
st.write("Response content:", response.text)
return None
else:
st.error(f"Error fetching data: {response.status_code} - {response.reason}")
st.error(
f"Error fetching data: {response.status_code} - "
f"{response.reason}"
)
st.write("Response content:", response.text)
return None
13 changes: 10 additions & 3 deletions src/data_processing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime


def process_solunar_data(data, date_str):
# Extract necessary data
try:
Expand All @@ -16,16 +17,22 @@ def process_solunar_data(data, date_str):
datetime_format = f'{date_format} {time_format}'

# Combine date and time
sunrise_dt = datetime.strptime(f"{date_str} {sunrise}", datetime_format)
sunrise_dt = datetime.strptime(
f"{date_str} {sunrise}", datetime_format
)
sunset_dt = datetime.strptime(f"{date_str} {sunset}", datetime_format)

if moonrise:
moonrise_dt = datetime.strptime(f"{date_str} {moonrise}", datetime_format)
moonrise_dt = datetime.strptime(
f"{date_str} {moonrise}", datetime_format
)
else:
moonrise_dt = None

if moonset:
moonset_dt = datetime.strptime(f"{date_str} {moonset}", datetime_format)
moonset_dt = datetime.strptime(
f"{date_str} {moonset}", datetime_format
)
else:
moonset_dt = None

Expand Down
93 changes: 84 additions & 9 deletions src/solunar_calculations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from datetime import timedelta
import math


def calculate_major_minor_times(solunar_data):
major_times = []
Expand All @@ -7,28 +9,101 @@ def calculate_major_minor_times(solunar_data):
if not solunar_data:
return major_times, minor_times

# Major times occur when the moon is overhead and underfoot
# Approximate times using moonrise and moonset
if solunar_data['moonrise']:
# Try to use moonrise and moonset data first
if solunar_data.get('moonrise') and solunar_data.get('moonset'):
moonrise = solunar_data['moonrise']
moonset = solunar_data['moonset']

# Major periods: moon overhead (moonrise) & underfoot (moonset)
major_times.append({
'start': moonrise - timedelta(hours=1),
'end': moonrise + timedelta(hours=1)
})

if solunar_data['moonset']:
moonset = solunar_data['moonset']
major_times.append({
'start': moonset - timedelta(hours=1),
'end': moonset + timedelta(hours=1)
})

# Minor Periods occur halfway between major periods
if len(major_times) == 2:
transit_time = major_times[0]['end'] + (major_times[1]['start'] - major_times[0]['end']) / 2
# Calculate minor periods (halfway between major periods)
# First minor: between moonset and next moonrise
if moonset < moonrise:
transit_time = moonset + (moonrise - moonset) / 2
else:
# If moonrise is before moonset, calculate for next day's cycle
transit_time = moonrise + timedelta(hours=6)

minor_times.append({
'start': transit_time - timedelta(minutes=30),
'end': transit_time + timedelta(minutes=30)
})

# Second minor: between moonrise and moonset
if moonset > moonrise:
transit_time2 = moonrise + (moonset - moonrise) / 2
else:
transit_time2 = moonset + timedelta(hours=6)
minor_times.append({
'start': transit_time2 - timedelta(minutes=30),
'end': transit_time2 + timedelta(minutes=30)
})

else:
# Fallback calculation when moonrise/moonset data is not available
# Use standard solunar theory approximations based on moon phase
moon_phase = solunar_data.get('moon_phase', 0.5)
sunrise = solunar_data.get('sunrise')
sunset = solunar_data.get('sunset')

if sunrise and sunset:
# Calculate approximate moon overhead times based on moon phase
# Full moon (0.5) rises at sunset and sets at sunrise
# New moon (0.0 or 1.0) rises at sunrise and sets at sunset

# Calculate approximate moonrise time based on phase
# This is a simplified calculation
moonrise_offset = 12 * moon_phase # hours after sunrise
approx_moonrise = sunrise + timedelta(hours=moonrise_offset)

# Moon overhead is approximately 6 hours after moonrise
moon_overhead = approx_moonrise + timedelta(hours=6)
# Moon underfoot is 12 hours after overhead
moon_underfoot = moon_overhead + timedelta(hours=12)

# Adjust times to be within the day
if moon_overhead.date() == sunrise.date():
major_times.append({
'start': moon_overhead - timedelta(hours=1),
'end': moon_overhead + timedelta(hours=1)
})

if moon_underfoot.date() == sunrise.date():
major_times.append({
'start': moon_underfoot - timedelta(hours=1),
'end': moon_underfoot + timedelta(hours=1)
})

# Calculate minor periods as midpoints
if len(major_times) >= 1:
# First minor is 6 hours before first major
minor1 = major_times[0]['start'] - timedelta(hours=5)
if minor1.date() == sunrise.date() and minor1 > sunrise:
minor_times.append({
'start': minor1 - timedelta(minutes=30),
'end': minor1 + timedelta(minutes=30)
})

# Second minor is 6 hours after first major
minor2 = major_times[0]['end'] + timedelta(hours=5)
sunset_plus_2 = sunset + timedelta(hours=2)
if (minor2.date() == sunrise.date() and
minor2 < sunset_plus_2):
minor_times.append({
'start': minor2 - timedelta(minutes=30),
'end': minor2 + timedelta(minutes=30)
})

# Sort all times by start time
major_times.sort(key=lambda x: x['start'])
minor_times.sort(key=lambda x: x['start'])

return major_times, minor_times
Loading