File size: 33,758 Bytes
bfb16bd
5ad5aa1
 
 
 
 
 
 
 
 
 
 
0ce5f64
 
da7510c
a1a6d07
d0caae6
7a6e135
 
 
d337775
5ad5aa1
d337775
2fa78cc
5ad5aa1
d337775
5ad5aa1
 
d337775
5ad5aa1
 
 
1b2af98
1214797
 
 
 
 
 
9c3f41a
939be35
 
 
9c3f41a
 
 
 
 
 
 
 
 
5ad5aa1
d337775
 
4f00e48
46b5e63
db1e1e0
939be35
 
 
 
 
909ee59
 
 
 
 
 
 
 
 
 
 
939be35
 
 
 
909ee59
 
939be35
909ee59
 
 
 
 
 
 
939be35
 
 
 
 
7eecc0f
939be35
 
 
 
7eecc0f
939be35
7eecc0f
5ad5aa1
d337775
 
d98eafe
5ad5aa1
d337775
 
460e177
1214797
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d337775
 
 
5ad5aa1
d337775
 
 
9c3f41a
 
 
 
 
 
d337775
 
1214797
 
 
 
 
9c3f41a
1214797
 
9c3f41a
 
 
 
 
 
 
 
 
 
 
 
 
1214797
9c3f41a
1214797
 
d337775
9c3f41a
5ad5aa1
 
 
d337775
5ad5aa1
d337775
5ad5aa1
aa0558a
5ad5aa1
 
 
 
d337775
 
5ad5aa1
 
 
9c3f41a
1214797
 
 
 
 
 
 
 
9c3f41a
5ad5aa1
 
 
 
 
 
 
9c3f41a
1214797
939be35
fcc71ef
bfb16bd
939be35
 
 
bfb16bd
 
 
 
7553d15
 
 
 
bfb16bd
7553d15
bfb16bd
 
 
 
 
909ee59
 
 
 
 
 
7553d15
 
 
 
fcc71ef
 
 
bfb16bd
fcc71ef
 
7553d15
fcc71ef
1214797
bfb16bd
7553d15
939be35
bfb16bd
 
7553d15
939be35
909ee59
fcc71ef
1214797
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ad5aa1
d337775
5ad5aa1
c51b0e8
 
d337775
c51b0e8
d337775
c51b0e8
 
d337775
c51b0e8
 
e42331a
 
c51b0e8
 
 
 
10fdea6
c51b0e8
 
 
 
10fdea6
c51b0e8
 
 
5f757bb
c51b0e8
 
 
d337775
c51b0e8
 
 
5f757bb
c51b0e8
 
 
 
5f757bb
c51b0e8
 
 
d337775
c51b0e8
 
d337775
c51b0e8
5f757bb
c51b0e8
d337775
 
 
68e35c2
 
 
 
 
d337775
 
68e35c2
 
 
 
bfa5f9b
 
 
d337775
 
 
 
 
 
 
 
 
 
 
 
 
e42331a
bfb16bd
 
 
939be35
bfb16bd
fcc71ef
bfb16bd
7553d15
bfb16bd
 
 
4d9f2c9
bfb16bd
 
 
 
 
 
939be35
7081cf2
d337775
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d98eafe
d337775
 
 
 
 
 
 
 
 
 
 
d98eafe
d337775
d98eafe
d337775
 
9c3f41a
 
d337775
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c3f41a
d337775
 
9c3f41a
 
d337775
 
 
 
 
 
 
 
 
 
 
 
 
9c3f41a
d337775
 
9c3f41a
 
d337775
 
9c3f41a
 
d337775
 
 
 
9c3f41a
 
d337775
 
 
 
 
9c3f41a
d337775
9c3f41a
 
d337775
 
 
 
 
 
 
 
9c3f41a
d337775
9c3f41a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ad5aa1
 
9c3f41a
 
6d957dc
 
 
 
9c3f41a
d337775
 
6d957dc
d337775
 
 
6d957dc
5ad5aa1
6d957dc
 
9c3f41a
6d957dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c3f41a
6d957dc
 
 
 
 
 
 
5ad5aa1
33c6528
1b2af98
 
5ad5aa1
d337775
5ad5aa1
 
 
 
d337775
5ad5aa1
d337775
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
import os
import streamlit as st  
import pandas as pd  
import requests  
import datetime  
import plotly.express as px  
import json  
import folium  
from streamlit_folium import folium_static  
import numpy as np  
import time  
from datetime import datetime, timedelta  
import pytz
import logging
import io


# Set up logging
logging.basicConfig(level=logging.DEBUG)

# --- API KEYS ---  
OPENWEATHER_API_KEY = "c6c267b301a145ddec9b381e7d87a5af"  
STORMGLASS_API_KEY = "bcabf6a8-0641-11f0-a4a9-0242ac130003-bcabf72a-0641-11f0-a4a9-0242ac130003"  # Page configuration


# --- PAGE CONFIGURATION ---  
st.set_page_config(page_title="Environmental Data Dashboard", layout="wide")  

# --- SIDEBAR ---  
st.sidebar.title("🌍 Environmental Data Dashboard")  
data_source = st.sidebar.selectbox(  
    "Select Data Source",  
    ["Weather Data", "Marine", "Solunar & Tide Data", "Fish Categories Data"]  
)

# Initialize global lists for each data source  
openweather_historical_readings = []  
openweather_forecast_readings = []  
stormglass_marine_readings = []  
solunar_tide_readings = []

# Initialize session state for historical weather data  
if 'openweather_historical_readings' not in st.session_state:  
    st.session_state.openweather_historical_readings = []

# Initialize session state for marine data  
if 'stormglass_marine_readings' not in st.session_state:  
    st.session_state.stormglass_marine_readings = []  

# Initialize session state for solunar and tide data  
if 'solunar_tide_readings' not in st.session_state:  
    st.session_state.solunar_tide_readings = []  

# --- API CALLING FUNCTIONS ---  
def fetch_openweather_data(lat, lon, datetime_input):  
    """Fetches historical weather data from OpenWeather's One Call API."""  
    try:  
        url = f"https://api.openweathermap.org/data/3.0/onecall/timemachine?lat={lat}&lon={lon}&dt={int(datetime_input.timestamp())}&appid={OPENWEATHER_API_KEY}&units=imperial"  
        response = requests.get(url)  
        response.raise_for_status()  
        data = response.json()  

        current_weather_data = data.get('data', [])[0] if data.get('data', []) else {}  
        # Extract sunrise and sunset timestamps and convert them to datetime objects  
        sunrise = current_weather_data.get('sunrise')  
        sunset = current_weather_data.get('sunset')  
        if sunrise:  
            sunrise_dt = datetime.fromtimestamp(sunrise)  
        else:  
            sunrise_dt = None  
        if sunset:  
            sunset_dt = datetime.fromtimestamp(sunset)  
        else:  
            sunset_dt = None
        processed_entry = {  
            'timestamp': datetime_input,  
            'latitude': lat,  
            'longitude': lon,  
            'temperature': current_weather_data.get('temp'),
            'feels_like': current_weather_data.get('feels_like'),
            'humidity': current_weather_data.get('humidity'),  
            'wind_speed': current_weather_data.get('wind_speed'),
            'wind_direction': current_weather_data.get('wind_deg'),
            'cloud_cover': current_weather_data.get('clouds'),
            'pressure': current_weather_data.get('pressure'),  
            'dew_point': current_weather_data.get('dew_point'),  
            'sunrise': sunrise_dt,  
            'sunset': sunset_dt
        }  

        # Append the processed entry to the session state list  
        st.session_state.openweather_historical_readings.append(processed_entry)  

        return data  
        # After fetching data  
        logging.debug(f"Processed Entry: {processed_entry}")  
        # When accessing historical data  
        logging.debug(f"Historical data count: {len(openweather_historical_readings)}")  
    except Exception as err:  
        st.error(f"An error occurred: {err}")    
        

def fetch_openweather_forecast(lat, lon):  
    """Fetches 7-day weather forecast data from OpenWeather's One Call API."""  
    url = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude=current,minutely,hourly,alerts&appid={OPENWEATHER_API_KEY}&units=imperial"  
    try:  
        response = requests.get(url)  
        response.raise_for_status()  # Raise HTTPError for bad responses  
        data = response.json()  

        # Store the forecast data in the global list  
        if data and 'daily' in data:  
            for day_data in data['daily']:  
                processed_entry = {  
                    'date': day_data.get('dt', None),  
                    'latitude': lat,  
                    'longitude': lon,  
                    'temperature_day': day_data.get('temp', {}).get('day', None),  
                    'temperature_night': day_data.get('temp', {}).get('night', None),  
                    'humidity': day_data.get('humidity', None),  
                    'wind_speed': day_data.get('wind_speed', None),  
                    # Add other relevant fields  
                }  
                openweather_forecast_readings.append(processed_entry)  

        return data 
  
    except requests.exceptions.RequestException as e:  
        st.error(f"Error fetching OpenWeather forecast data: {e}")  
        return None  

def fetch_stormglass_data(lat, lng, start_datetime, end_datetime, selected_params):  
    """Fetches marine data from Stormglass API."""  
    try:  
        params_str = ','.join(selected_params)  
        start_str = start_datetime.isoformat()  
        end_str = end_datetime.isoformat()  
        url = f"https://api.stormglass.io/v2/weather/point?lat={lat}&lng={lng}&params={params_str}&start={start_str}&end={end_str}"  
        headers = {'Authorization': STORMGLASS_API_KEY}  

        response = requests.get(url, headers=headers)  
        response.raise_for_status()  
        data = response.json()  

        if data and 'hours' in data:  
            for hour_data in data['hours']:  
                processed_entry = {  
                    'timestamp': hour_data.get('time'),  
                    'latitude': lat,  
                    'longitude': lng,  
                    'waveHeight': hour_data.get('waveHeight', {}).get('value'),  
                    'wavePeriod': hour_data.get('wavePeriod', {}).get('value'),  
                    'waveDirection': hour_data.get('waveDirection', {}).get('value'),  
                    'windSpeed': hour_data.get('windSpeed', {}).get('value'),  
                    'windDirection': hour_data.get('windDirection', {}).get('value'),  
                    'airTemperature': hour_data.get('airTemperature', {}).get('value'),  
                    'waterTemperature': hour_data.get('waterTemperature', {}).get('value'),  
                    'seaLevel': hour_data.get('seaLevel', {}).get('value'),  
                    'humidity': hour_data.get('humidity', {}).get('value'),  
                    'precipitation': hour_data.get('precipitation', {}).get('value'),  
                    'visibility': hour_data.get('visibility', {}).get('value'),  
                    'currentSpeed': hour_data.get('currentSpeed', {}).get('value'),  
                    'currentDirection': hour_data.get('currentDirection', {}).get('value')  
                }  
                st.session_state.stormglass_marine_readings.append(processed_entry)  

        return data  
    except requests.exceptions.RequestException as e:  
        st.error(f"Error fetching Stormglass marine data: {e}")  
        return None  

def fetch_solunar_tide_data(lat, lon, datetime_input):  
    """Fetches solunar and tide data from Stormglass API."""  
    try:  
        headers = {'Authorization': STORMGLASS_API_KEY}  
        params = {  
            'lat': lat,  
            'lng': lon,  
            'date': datetime_input.date().isoformat()  
        }  

        tide_response = requests.get('https://api.stormglass.io/v2/tide/extremes/point', headers=headers, params=params)  
        astronomy_response = requests.get('https://api.stormglass.io/v2/astronomy/point', headers=headers, params=params)  

        tide_data = tide_response.json() if tide_response.status_code == 200 else None  
        astronomy_data = astronomy_response.json() if astronomy_response.status_code == 200 else None  

        if tide_data or astronomy_data:  
            processed_entry = {  
                'date': datetime_input.date().isoformat(),  
                'latitude': lat,  
                'longitude': lon,  
                'tide_data': tide_data,  
                'astronomy_data': astronomy_data  
            }  
            st.session_state.solunar_tide_readings.append(processed_entry)  

        return {  
            'tide': tide_data,  
            'astronomy': astronomy_data  
        }  
    except Exception as e:  
        st.error(f"Error fetching Solunar and Tide data: {e}")  
        return None  

def save_openweather_historical_to_csv():  
    """Save historical OpenWeather data to CSV for download."""  
    try:  
        # Access the historical data from session state  
        openweather_historical_readings = st.session_state.openweather_historical_readings  

        # Validate input data  
        if not openweather_historical_readings:  
            raise ValueError("No historical data available.")  
        
        # Ensure the data is in the correct format (list of dictionaries)  
        if not isinstance(openweather_historical_readings, list):  
            raise ValueError("Historical readings must be a list of dictionaries.")  
        
        # Create a DataFrame from the readings  
        df = pd.DataFrame(openweather_historical_readings)  
        
        # Ensure the DataFrame is not empty  
        if df.empty:  
            raise ValueError("DataFrame is empty.")  
        
        # Convert datetime columns to strings  
        datetime_columns = ['timestamp', 'sunrise', 'sunset']  
        for col in datetime_columns:  
            if col in df.columns:  
                df[col] = df[col].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if x is not None else None)  

        # Log DataFrame preview for debugging  
        logging.debug("DataFrame Preview:")  
        logging.debug(df.head())  
        
        # Create a CSV buffer in memory  
        csv_buffer = io.StringIO()  
        df.to_csv(csv_buffer, index=False)  
        
        # Return the CSV data as a string  
        csv_data = csv_buffer.getvalue()  
        logging.debug("CSV data generated successfully.")  
        return csv_data  
    
    except ValueError as ve:  
        logging.error(f"Validation error: {ve}")  
        st.error(f"Validation error: {ve}")  
        return None  
    except Exception as e:  
        logging.error(f"Error saving historical data: {str(e)}")  
        st.error(f"Error saving historical data: {str(e)}")  
        return None    
  

def save_stormglass_marine_to_csv():  
    """Save Stormglass marine data to CSV."""  
    if not stormglass_marine_readings:  
        st.error("No marine data to save!")  
        return  
    
    df = pd.DataFrame(stormglass_marine_readings)  
    return df  

def save_solunar_tide_to_csv():  
    """Save solunar and tide data to CSV."""  
    if not solunar_tide_readings:  
        st.error("No solunar or tide data to save!")  
        return  
    
    df = pd.DataFrame(solunar_tide_readings)  
    return df


# --- MAIN APPLICATION LOGIC ---  
def main():  
    if data_source == "Weather Data":
        st.title("OpenWeather Data")  

        col1, col2 = st.columns(2)  

        with col1:  
            st.subheader("Location and Time Settings")  

            # Latitude and Longitude inputs  
            lat = st.number_input("Latitude", value=25.7617, step=0.001, format="%.4f")  
            lon = st.number_input("Longitude", value=-80.1918, step=0.001, format="%.4f")

            # Manage session state for date and time inputs  
            if 'datetime_input' not in st.session_state:  
                # Initialize with current date and time if not already present  
                st.session_state['datetime_input'] = datetime.now()  

            # Split the stored datetime into date and time components  
            datetime_input = st.session_state['datetime_input']  
            input_date = datetime_input.date()  # Use the date part  
            input_time = datetime_input.time()   # Use the time part  

            # Allow user to set new date and time  
            input_date = st.date_input("Date", value=input_date)  
            input_time = st.time_input("Time", value=input_time)  

            # Combine date and time into a datetime object  
            datetime_input = datetime.combine(input_date, input_time)  
            st.session_state['datetime_input'] = datetime_input  # Store the datetime in the session state  

            if st.button("Get Current Weather"):  
                with st.spinner("Fetching data..."):  
                    data = fetch_openweather_data(lat, lon, datetime_input)  

                    if data and 'data' in data:  
                        current_weather_data = data['data']  
                        if current_weather_data:  
                            current = current_weather_data[0]  

                            # Convert and format sunrise and sunset  
                            timezone = data.get('timezone', 'UTC')  
                            tz = pytz.timezone(timezone)  

                            sunrise_time = datetime.fromtimestamp(current['sunrise'], tz).strftime('%Y-%m-%d %H:%M:%S')  
                            sunset_time = datetime.fromtimestamp(current['sunset'], tz).strftime('%Y-%m-%d %H:%M:%S')  

                            st.subheader(f"Weather at {lat}, {lon} on {datetime_input}")  

                            weather_col1, weather_col2 = st.columns(2)  


                        with weather_col1:  
                            st.metric("Temperature", f"{current['temp']}°F")  
                            st.metric("Feels Like", f"{current['feels_like']}°F")  
                            st.metric("Humidity", f"{current['humidity']}%")  
                            st.metric("Pressure", f"{current['pressure']} in")
                            st.metric("Dew Point", f"{current['dew_point']} °F")

                        with weather_col2:  
                            st.metric("Wind Speed", f"{current['wind_speed']} mph")  
                            st.metric("Wind Direction", f"{current['wind_deg']}Rad")  
                            #st.metric("UV Index", f"{current['uvi']}")  
                            st.metric("Cloud Cover", f"{current.get('clouds', 'N/A')}%")
                        # Display sunrise and sunset times  
                            st.write(f"**Sunrise:** {sunrise_time}")  
                            st.write(f"**Sunset:** {sunset_time}")  

                        # Weather description  
                        if 'weather' in current and current['weather']:  
                            st.write(f"**Conditions:** {current['weather'][0]['description']}")  

                        # Map display  
                        m = folium.Map(location=[lat, lon], zoom_start=10)  
                        folium.Marker(  
                            location=[lat, lon],  
                            popup=f"{lat}, {lon}",  
                            icon=folium.Icon(color="red")  
                        ).add_to(m)  
                        folium_static(m)  

        
        # Download button for historical data  
        if st.button("Download Historical Weather Data"):  
            csv_data = save_openweather_historical_to_csv()  
    
            if csv_data:  
                try:  
                    # Create a download button  
                    st.download_button(  
                        label="Download Historical Weather Data CSV",  
                        data=csv_data,  
                        file_name=f"openweather_historical_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",  
                        mime="text/csv",  
                    )  
                    st.success("Historical weather data saved and ready for download!")  
                except Exception as e:  
                    st.error(f"Error preparing download: {str(e)}")  
            else:  
                    st.error("No historical data available.")    
 

        with col2:  
            if st.button("Get Weather Forecast"):  
                with st.spinner("Fetching forecast data..."):  
                    forecast_data = fetch_openweather_forecast(lat, lon)  

                    if forecast_data and 'daily' in forecast_data:  
                        st.subheader(f"7-Day Weather Forecast for {lat}, {lon}")  

                        # Prepare forecast data  
                        forecast_list = []  

                        for daily_forecast in forecast_data['daily']:  
                            forecast_list.append({  
                                'date': datetime.fromtimestamp(daily_forecast['dt']),  
                                'temperature_day': daily_forecast['temp']['day'],  
                                'temperature_night': daily_forecast['temp']['night'],  
                                'feels_like_day': daily_forecast['feels_like']['day'],  
                                'humidity': daily_forecast['humidity'],  
                                'wind_speed': daily_forecast['wind_speed'],  
                                'description': daily_forecast['weather'][0]['description']  
                            })  

                        forecast_df = pd.DataFrame(forecast_list)  

                        # Temperature plot  
                        fig_temp = px.line(forecast_df, x='date', y=['temperature_day', 'temperature_night'],  
                                          title="Daily Temperature Forecast",  
                                          labels={'value': 'Temperature (°F)', 'date': 'Date'},  
                                          color_discrete_map={'temperature_day': 'red', 'temperature_night': 'blue'})  
                        st.plotly_chart(fig_temp, use_container_width=True)  

                        # Forecast details  
                        for forecast in forecast_list:  
                            col_date, col_details = st.columns([1, 3])  

                            with col_date:  
                                st.write(f"**{forecast['date'].strftime('%Y-%m-%d')}**")  

                            with col_details:  
                                st.write(f"Day Temp: {forecast['temperature_day']}°F | Night Temp: {forecast['temperature_night']}°F")  
                                st.write(f"Conditions: {forecast['description']}")  
                                st.write(f"Humidity: {forecast['humidity']}% | Wind: {forecast['wind_speed']} mph")  

    elif data_source == "Marine":  
        st.title("Stormglass Marine & Weather Data")
        st.subheader("Detailed Marine Weather Conditions and Forecasts")

        col1, col2 = st.columns(2)  

        with col1:  
            st.subheader("Location Settings")  

            lat = st.number_input("Latitude", value=25.7617, step=0.001, format="%.4f")  
            lng = st.number_input("Longitude", value=-80.1918, step=0.001, format="%.4f")  

            # Map for selecting location  
            map_data = pd.DataFrame({  
                'lat': [lat],  
                'lon': [lng]  
            })  

            st.map(map_data)  

            # Date range selection  
            st.subheader("Time Range")  



            col_start, col_end = st.columns(2)  

            with col_start:  
                start_date = st.date_input("Start Date", datetime.now())  
                start_time = st.time_input("Start Time", datetime.time(datetime.now()))  

            with col_end:  
                end_date = st.date_input("End Date", datetime.now() + timedelta(days=1))  
                end_time = st.time_input("End Time", datetime.time(datetime.now()))  

            # Combine date and time  
            start_datetime = datetime.combine(start_date, start_time)  
            end_datetime = datetime.combine(end_date, end_time)  

            # Parameter selection  
            st.subheader("Data Parameters")  

            available_params = [  
                'waveHeight', 'wavePeriod', 'waveDirection',  
                'windSpeed', 'windDirection', 'airTemperature',  
                'waterTemperature', 'seaLevel', 'humidity',  
                'precipitation', 'visibility', 'currentSpeed',  
                'currentDirection'  
            ]  

            selected_params = st.multiselect(  
                "Select Parameters",  
                available_params,  
                default=['waveHeight', 'windSpeed', 'airTemperature', 'waterTemperature']  
            )  

            if st.button("Get Stormglass Data"):  
                if not selected_params:  
                    st.warning("Please select at least one parameter.")  
                else:  
                    with st.spinner("Fetching Stormglass data..."):  
                        stormglass_data = fetch_stormglass_data(  
                            lat,  
                            lng,  
                            start_datetime,  
                            end_datetime,  
                            selected_params  
                        )  

                        # After fetching data, process and display it  
                        if stormglass_data and 'hours' in stormglass_data:  
                            st.success(f"Retrieved {len(stormglass_data['hours'])} hours of data")  
                        
                            # Process data for display  
                            processed_data = []  
                            for hour_data in stormglass_data['hours']:  
                                hour_record = {'time': hour_data['time']}  
                                for param in selected_params:  
                                    if param in hour_data:  
                                        sources = hour_data[param]  
                                        values = [v for k, v in sources.items() if v is not None]  
                                        if values:  
                                            hour_record[f"{param}_avg"] = sum(values) / len(values)  
                                            for source, value in sources.items():  
                                                if value is not None:  
                                                    hour_record[f"{param}_{source}"] = value  
                                processed_data.append(hour_record)  
                        
                            sg_df = pd.DataFrame(processed_data)  
                            sg_df['time'] = pd.to_datetime(sg_df['time'])  
                        
                            # Show raw data in an expander  
                            with st.expander("Show Raw Data"):  
                                st.dataframe(sg_df)  
                        
                            # Create interactive visualizations  
                            for param in selected_params:  
                                avg_col = f"{param}_avg"  
                                if avg_col in sg_df.columns:  
                                    param_sources = [col for col in sg_df.columns if col.startswith(f"{param}_") and col != avg_col]  
                        
                                    # Main plot with average values  
                                    fig = px.line(  
                                        sg_df,  
                                        x='time',  
                                        y=avg_col,  
                                        title=f"{param} (Average)",  
                                        labels={'time': 'Time', avg_col: param.capitalize()}  
                                    )  
                        
                                    # Add individual sources to the plot  
                                    for source_col in param_sources:  
                                        source_name = source_col.replace(f"{param}_", "")  
                                        fig.add_scatter(  
                                            x=sg_df['time'],  
                                            y=sg_df[source_col],  
                                            mode='lines',  
                                            name=source_name  
                                        )  
                        
                                    st.plotly_chart(fig, use_container_width=True)  
                        
                            # Display statistics for each parameter  
                            with st.expander("View Data Statistics"):  
                                for param in selected_params:  
                                    avg_col = f"{param}_avg"  
                                    if avg_col in sg_df.columns:  
                                        stats = sg_df[avg_col].describe()  
                                        st.write(f"**Statistics for {param.capitalize()}:**")  
                                        st.write(f"Minimum: {stats['min']:.2f}")  
                                        st.write(f"Maximum: {stats['max']:.2f}")  
                                        st.write(f"Mean: {stats['mean']:.2f}")  
                                        st.write(f"Median: {stats['50%']:.2f}")  
                                        st.write(f"Standard Deviation: {stats['std']:.2f}")  
                                        st.markdown("---")  
                        
                            # Additional visualizations  
                            if 'waveHeight_avg' in sg_df.columns and 'windSpeed_avg' in sg_df.columns:  
                                st.subheader("Wave Height vs Wind Speed")  
                                scatter_fig = px.scatter(  
                                    sg_df,  
                                    x='windSpeed_avg',  
                                    y='waveHeight_avg',  
                                    title="Correlation: Wave Height vs Wind Speed",  
                                    labels={'windSpeed_avg': 'Wind Speed', 'waveHeight_avg': 'Wave Height'}  
                                )  
                                st.plotly_chart(scatter_fig, use_container_width=True)  
                        
                            # Time of day analysis if sufficient data is available  
                            if len(sg_df) >= 24:  
                                st.subheader("Time of Day Analysis")  
                                sg_df['hour'] = sg_df['time'].dt.hour  
                        
                                for param in selected_params:  
                                    avg_col = f"{param}_avg"  
                                    if avg_col in sg_df.columns:  
                                        hourly_avg = sg_df.groupby('hour')[avg_col].mean().reset_index()  
                                        hour_fig = px.line(  
                                            hourly_avg,  
                                            x='hour',  
                                            y=avg_col,  
                                            title=f"{param.capitalize()} by Hour of Day",  
                                            labels={'hour': 'Hour of Day', avg_col: param.capitalize()}  
                                        )  
                                        st.plotly_chart(hour_fig, use_container_width=True)    

    elif data_source == "Solunar & Tide Data":  
        st.header("🌞 Solunar and Tide Information")  
        st.subheader("Detailed Solunar and Tide Conditions")  
        
        # Initialize astro_data to None  
        astro_data = None  
        
        # Location and date/time input  
        lat = st.number_input("Latitude", value=25.7617, step=0.001, format="%.4f")  
        lon = st.number_input("Longitude", value=-80.1918, step=0.001, format="%.4f")  
        
        input_date = st.date_input("Date", datetime.now())  
        input_time = st.time_input("Time", datetime.time(datetime.now()))  
        datetime_input = datetime.combine(input_date, input_time)  
        
        if st.button("Retrieve Solunar and Tide Data"):  
            try:  
                solunar_tide_data = fetch_solunar_tide_data(lat, lon, datetime_input)  
                
                if solunar_tide_data:  
                    # Process tide data  
                    if solunar_tide_data['tide'] and 'data' in solunar_tide_data['tide']:  
                        st.subheader("Tide Times")  
                        tide_df = pd.DataFrame(solunar_tide_data['tide']['data'])  
                        
                        for _, tide in tide_df.iterrows():  
                            st.write(f"**{tide['type'].capitalize()} Tide:**")  
                            tide_time = pd.to_datetime(tide['time'])  
                            st.write(f"Time: {tide_time.strftime('%Y-%m-%d %H:%M:%S')}")  
                            st.write(f"Height: {tide.get('height', 'N/A')} meters")  
                    
                    # Process astronomy data  
                    if solunar_tide_data['astronomy'] and 'data' in solunar_tide_data['astronomy']:  
                        astro_data = solunar_tide_data['astronomy']['data']  
                        
                        st.subheader("Sun and Moon Information")  
                        col1, col2 = st.columns(2)  
                        with col1:  
                            sunrise_time = pd.to_datetime(astro_data.get('sunrise'))  
                            st.metric("Sunrise", sunrise_time.strftime('%H:%M') if sunrise_time else 'N/A')  
                            st.metric("Solar Noon", astro_data.get('solarNoon', 'N/A'))  
                        with col2:  
                            sunset_time = pd.to_datetime(astro_data.get('sunset'))  
                            st.metric("Sunset", sunset_time.strftime('%H:%M') if sunset_time else 'N/A')  
                            st.metric("Day Length", astro_data.get('dayLength', 'N/A'))  
                    
                    # Additional astronomy details  
                    if astro_data is not None:  
                        if 'moonPhase' in astro_data:  
                            st.subheader("Moon Phase Visualization")  
                            moon_phase = astro_data['moonPhase']  
                            fig = go.Figure(go.Indicator(  
                                mode="gauge+number+delta",  
                                value=moon_phase,  
                                title={'text': "Moon Phase"},  
                                delta={'reference': 0.5, 'increasing': {'color': "RebeccaPurple"}},  
                                gauge={  
                                    'axis': {'range': [None, 1], 'visible': False},  
                                    'bar': {'color': "lightgray"},  
                                    'bgcolor': "lightgray",  
                                    'borderwidth': 0,  
                                    'width': 0.25  
                                }  
                            ))  
                            st.plotly_chart(fig, use_container_width=True)  
                        
                        if 'moonAge' in astro_data:  
                            st.write(f"**Moon Age:** {astro_data['moonAge']} days")  
                    
                    # Tide level visualization  
                    if solunar_tide_data['tide'] and 'data' in solunar_tide_data['tide']:  
                        st.subheader("Tide Level Visualization")  
                        tide_df = pd.DataFrame(solunar_tide_data['tide']['data'])  
                        tide_df['time'] = pd.to_datetime(tide_df['time'])  
                        tide_df['height'] = pd.to_numeric(tide_df['height'], errors='coerce')  
                        
                        tide_fig = px.line(  
                            tide_df,  
                            x='time',  
                            y='height',  
                            title="Tide Levels Over Time",  
                            labels={'time': 'Time', 'height': 'Tide Height (m)'}  
                        )  
                        st.plotly_chart(tide_fig, use_container_width=True)  
                
                # Handle cases where no data is returned  
                if not solunar_tide_data or ('tide' not in solunar_tide_data and 'astronomy' not in solunar_tide_data):  
                    st.warning("No solunar or tide data available for the specified date and location.")  
                    
            except Exception as e:  
                st.error(f"An error occurred while fetching solunar and tide data: {str(e)}")  
                logging.error(f"Error in solunar and tide data fetching: {str(e)}")        

    elif data_source == "Fish Categories Data":  
        st.header("🐟 Fish Categires Data")  
        st.warning("Fish Categires Data functionality to be implemented")  

    # Footer  
    st.sidebar.markdown("---")  
    st.sidebar.markdown("🌍 Environmental Data Dashboard")  
    st.sidebar.markdown("Powered by Streamlit & Open APIs")  

# --- RUN THE MAIN APPLICATION ---  
if __name__ == "__main__":  
    main()