Update app.py
Browse files
app.py
CHANGED
@@ -580,164 +580,179 @@ def add_coordinates_to_incidents(df, max_incidents=30):
|
|
580 |
print(f"Successfully extracted coordinates for {success_count}/{len(sample_df)} incidents")
|
581 |
return df
|
582 |
|
583 |
-
# Enhanced map generation
|
584 |
def generate_enhanced_map(df, firms_df):
|
585 |
-
"""Generate map
|
586 |
|
587 |
try:
|
588 |
-
print("Starting map generation...")
|
589 |
|
590 |
# Create map centered on the US
|
591 |
m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
|
592 |
|
593 |
-
#
|
594 |
-
|
595 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
596 |
try:
|
597 |
-
# Limit to first 1000 hotspots for performance
|
598 |
-
sample_firms = firms_df.head(1000)
|
599 |
heat_data = []
|
600 |
-
|
601 |
-
for _, row in sample_firms.iterrows():
|
602 |
try:
|
603 |
-
lat, lon = float(
|
604 |
-
frp = float(row.get('frp', 1))
|
605 |
if -90 <= lat <= 90 and -180 <= lon <= 180: # Valid coordinates
|
606 |
heat_data.append([lat, lon, min(frp, 100)])
|
607 |
-
except (ValueError, TypeError):
|
608 |
continue
|
609 |
|
610 |
if heat_data:
|
611 |
HeatMap(
|
612 |
heat_data,
|
613 |
-
name="Fire Intensity
|
614 |
radius=15,
|
615 |
blur=10,
|
616 |
max_zoom=1,
|
617 |
gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}
|
618 |
).add_to(m)
|
619 |
-
print(f"Added heatmap with {len(heat_data)}
|
620 |
|
621 |
-
# Add
|
622 |
-
for i,
|
623 |
try:
|
624 |
-
lat, lon = float(
|
625 |
-
frp = float(hotspot.get('frp', 1))
|
626 |
-
conf = hotspot.get('confidence', 'N/A')
|
627 |
-
acq_time = hotspot.get('acq_time', 'N/A')
|
628 |
|
629 |
if -90 <= lat <= 90 and -180 <= lon <= 180:
|
630 |
folium.CircleMarker(
|
631 |
location=[lat, lon],
|
632 |
radius=2 + min(frp / 10, 8),
|
633 |
-
popup=f"π₯
|
634 |
color='red',
|
635 |
fillColor='orange',
|
636 |
fillOpacity=0.7,
|
637 |
weight=1
|
638 |
).add_to(m)
|
639 |
-
except (ValueError, TypeError,
|
640 |
continue
|
641 |
|
642 |
except Exception as e:
|
643 |
print(f"Error adding FIRMS data to map: {e}")
|
644 |
|
645 |
-
# Add incident markers
|
646 |
-
|
647 |
-
|
648 |
-
active_incidents = 0
|
649 |
-
inactive_incidents = 0
|
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 |
-
|
692 |
-
|
|
|
|
|
|
|
693 |
|
694 |
-
|
695 |
-
|
696 |
-
<h4>{name}</h4>
|
697 |
-
<b>Type:</b> {incident_type}<br>
|
698 |
-
<b>Location:</b> {location}<br>
|
699 |
-
<b>Size:</b> {size} acres<br>
|
700 |
-
<b>Last Updated:</b> {updated}<br>
|
701 |
-
|
702 |
-
<hr style="margin: 10px 0;">
|
703 |
-
<h5>π₯ Fire Activity (NASA FIRMS)</h5>
|
704 |
-
<b>Status:</b> {'π΄ ACTIVE' if is_active else 'β« Inactive'}<br>
|
705 |
-
<b>Activity Level:</b> {activity_level}<br>
|
706 |
-
<b>Hotspots (24h):</b> {firms_hotspots}<br>
|
707 |
-
<b>Total Fire Power:</b> {total_frp:.1f} MW<br>
|
708 |
-
<b>Avg Confidence:</b> {avg_confidence:.1f}%<br>
|
709 |
</div>
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
print("No InciWeb incidents have coordinates, showing FIRMS data only")
|
726 |
-
inactive_incidents = len(df) if not df.empty else 0
|
727 |
|
728 |
-
# Add
|
729 |
-
|
730 |
-
|
731 |
|
732 |
legend_html = f'''
|
733 |
<div style="position: fixed;
|
734 |
-
bottom: 50px; left: 50px; width:
|
735 |
border:2px solid grey; z-index:9999; font-size:12px;
|
736 |
background-color:white; padding: 10px;
|
737 |
border-radius: 5px; font-family: Arial;">
|
738 |
-
<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">π₯ Wildfire
|
739 |
|
740 |
-
<div style="margin-bottom: 8px;"><b>
|
741 |
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
742 |
<div style="background-color: red; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
743 |
<div>Very High Activity</div>
|
@@ -750,29 +765,25 @@ def generate_enhanced_map(df, firms_df):
|
|
750 |
<div style="background-color: yellow; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
751 |
<div>Medium Activity</div>
|
752 |
</div>
|
753 |
-
<div style="display: flex; align-items: center; margin-bottom:
|
754 |
<div style="background-color: lightcoral; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
755 |
<div>Low Activity</div>
|
756 |
</div>
|
757 |
-
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
758 |
-
<div style="background-color: gray; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
759 |
-
<div>Inactive/No Data</div>
|
760 |
-
</div>
|
761 |
|
762 |
-
<div style="margin-bottom: 5px;"><b>
|
763 |
-
<div style="display: flex; align-items: center; margin-bottom:
|
764 |
<div style="background-color: orange; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
765 |
-
<div>
|
766 |
</div>
|
767 |
-
<div style="margin-bottom: 8px; font-style: italic;">Heat map shows fire intensity</div>
|
768 |
|
769 |
<div style="font-size: 11px; margin-top: 10px; padding-top: 5px; border-top: 1px solid #ccc;">
|
770 |
-
<b
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
|
|
776 |
</div>
|
777 |
</div>
|
778 |
'''
|
@@ -787,194 +798,251 @@ def generate_enhanced_map(df, firms_df):
|
|
787 |
try:
|
788 |
map_html = m._repr_html_()
|
789 |
map_with_legend = map_html.replace('</body>', legend_html + '</body>')
|
790 |
-
print("Map generation completed successfully")
|
791 |
return map_with_legend
|
792 |
except Exception as e:
|
793 |
print(f"Error generating final map HTML: {e}")
|
794 |
return f"<div style='padding: 20px; text-align: center;'>Map generation error: {str(e)}</div>"
|
795 |
|
796 |
except Exception as e:
|
797 |
-
print(f"Critical error in map generation: {e}")
|
798 |
import traceback
|
799 |
traceback.print_exc()
|
800 |
return f"<div style='padding: 20px; text-align: center;'>Critical map error: {str(e)}</div>"
|
801 |
|
802 |
-
# Enhanced visualization functions
|
803 |
def generate_enhanced_visualizations(df, firms_df):
|
804 |
-
"""Generate enhanced visualizations
|
805 |
figures = []
|
806 |
|
807 |
try:
|
808 |
-
print("Starting visualization generation...")
|
809 |
|
810 |
if df.empty:
|
811 |
print("Warning: Empty dataframe for visualizations")
|
812 |
return [px.bar(title="No data available")]
|
813 |
|
814 |
-
#
|
815 |
-
|
816 |
-
if 'is_active' in df.columns:
|
817 |
-
activity_counts = df['is_active'].value_counts()
|
818 |
-
if not activity_counts.empty:
|
819 |
-
activity_summary = activity_counts.reset_index()
|
820 |
-
activity_summary.columns = ['is_active', 'count']
|
821 |
-
activity_summary['status'] = activity_summary['is_active'].map({
|
822 |
-
True: 'Active (FIRMS detected)',
|
823 |
-
False: 'Inactive/Unknown'
|
824 |
-
})
|
825 |
-
|
826 |
-
fig1 = px.pie(
|
827 |
-
activity_summary,
|
828 |
-
values='count',
|
829 |
-
names='status',
|
830 |
-
title="π₯ Wildfire Activity Status (Based on NASA FIRMS Data)",
|
831 |
-
color_discrete_map={
|
832 |
-
'Active (FIRMS detected)': 'red',
|
833 |
-
'Inactive/Unknown': 'gray'
|
834 |
-
}
|
835 |
-
)
|
836 |
-
fig1.update_traces(textinfo='label+percent+value')
|
837 |
-
else:
|
838 |
-
fig1 = px.bar(title="No activity status data available")
|
839 |
-
else:
|
840 |
-
fig1 = px.bar(title="Activity status data not available")
|
841 |
-
except Exception as e:
|
842 |
-
print(f"Error creating activity status chart: {e}")
|
843 |
-
fig1 = px.bar(title=f"Activity status error: {str(e)}")
|
844 |
-
figures.append(fig1)
|
845 |
|
846 |
-
#
|
847 |
try:
|
848 |
-
if
|
849 |
-
|
850 |
-
|
851 |
|
852 |
-
|
853 |
-
|
854 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
855 |
y='count',
|
856 |
-
title="
|
857 |
-
labels={'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
858 |
)
|
859 |
else:
|
860 |
-
|
861 |
except Exception as e:
|
862 |
-
print(f"Error creating
|
863 |
-
|
864 |
-
figures.append(
|
865 |
|
866 |
-
#
|
867 |
try:
|
868 |
-
if
|
869 |
-
state_counts =
|
870 |
state_counts.columns = ['state_name', 'count']
|
871 |
|
872 |
-
|
873 |
state_counts,
|
874 |
x='state_name',
|
875 |
y='count',
|
876 |
-
title="πΊοΈ
|
877 |
-
labels={'state_name': 'State', 'count': '
|
|
|
|
|
|
|
|
|
|
|
|
|
878 |
)
|
879 |
else:
|
880 |
-
|
881 |
except Exception as e:
|
882 |
print(f"Error creating state distribution chart: {e}")
|
883 |
-
|
884 |
-
figures.append(
|
885 |
|
886 |
-
#
|
887 |
try:
|
888 |
-
if not
|
889 |
-
#
|
890 |
-
|
891 |
-
|
892 |
-
|
|
|
|
|
893 |
|
894 |
-
if not
|
895 |
-
|
896 |
-
|
897 |
-
x='
|
898 |
-
y='
|
899 |
-
|
900 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
901 |
)
|
902 |
-
fig4.update_traces(line_color='red')
|
903 |
else:
|
904 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
905 |
else:
|
906 |
fig4 = px.bar(title="FIRMS temporal data not available")
|
907 |
except Exception as e:
|
908 |
-
print(f"Error creating
|
909 |
-
fig4 = px.bar(title=f"
|
910 |
figures.append(fig4)
|
911 |
|
912 |
-
# 5.
|
913 |
try:
|
914 |
-
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
924 |
-
|
925 |
-
|
926 |
-
|
927 |
-
|
928 |
-
|
|
|
|
|
929 |
}
|
930 |
-
|
931 |
-
|
932 |
-
|
933 |
-
x='activity_level',
|
934 |
-
y='count',
|
935 |
-
title="π Fire Activity Levels (NASA FIRMS Intensity)",
|
936 |
-
labels={'activity_level': 'Activity Level', 'count': 'Number of Incidents'},
|
937 |
-
color='activity_level',
|
938 |
-
color_discrete_map=color_map,
|
939 |
-
category_orders={'activity_level': level_order}
|
940 |
-
)
|
941 |
-
else:
|
942 |
-
fig5 = px.bar(title="No activity level data with known values")
|
943 |
else:
|
944 |
-
fig5 = px.bar(title="
|
945 |
except Exception as e:
|
946 |
-
print(f"Error creating
|
947 |
-
fig5 = px.bar(title=f"
|
948 |
figures.append(fig5)
|
949 |
|
950 |
-
print(f"Generated {len(figures)} visualizations")
|
951 |
return figures
|
952 |
|
953 |
except Exception as e:
|
954 |
-
print(f"Critical error in visualization generation: {e}")
|
955 |
import traceback
|
956 |
traceback.print_exc()
|
957 |
return [px.bar(title=f"Critical visualization error: {str(e)}")]
|
958 |
|
959 |
# Main application function
|
960 |
-
def
|
961 |
-
"""Create the
|
962 |
|
963 |
-
with gr.Blocks(title="
|
964 |
gr.Markdown("""
|
965 |
-
# π₯
|
966 |
## InciWeb Incidents + NASA FIRMS Real-Time Fire Detection
|
967 |
|
968 |
-
This application
|
969 |
-
|
970 |
-
|
971 |
-
-
|
972 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
973 |
""")
|
974 |
|
975 |
with gr.Row():
|
976 |
-
fetch_btn = gr.Button("π Fetch
|
977 |
-
status_text = gr.Textbox(label="Status", interactive=False, value="Ready to fetch data...")
|
978 |
|
979 |
with gr.Tabs():
|
980 |
with gr.TabItem("πΊοΈ Enhanced Map"):
|
@@ -984,23 +1052,25 @@ def create_enhanced_wildfire_app():
|
|
984 |
with gr.Row():
|
985 |
plot_selector = gr.Dropdown(
|
986 |
choices=[
|
987 |
-
"
|
988 |
-
"
|
989 |
-
"Intensity vs Size
|
990 |
"Hotspot Detection Timeline",
|
991 |
-
"
|
992 |
],
|
993 |
label="Select Visualization",
|
994 |
-
value="
|
995 |
)
|
996 |
-
plot_display = gr.Plot(label="Enhanced Analytics")
|
997 |
|
998 |
with gr.TabItem("π Data Tables"):
|
999 |
with gr.Tabs():
|
1000 |
-
with gr.TabItem("
|
1001 |
-
|
1002 |
-
with gr.TabItem("
|
1003 |
-
|
|
|
|
|
1004 |
|
1005 |
with gr.TabItem("π Export Data"):
|
1006 |
gr.Markdown("### Download Enhanced Dataset")
|
@@ -1014,22 +1084,22 @@ def create_enhanced_wildfire_app():
|
|
1014 |
def fetch_and_process_data():
|
1015 |
"""Main data processing function with comprehensive error handling and debugging"""
|
1016 |
try:
|
1017 |
-
yield "π‘ Fetching InciWeb incident data...", None, None, None, None, None, None
|
1018 |
|
1019 |
# Fetch InciWeb data with error handling
|
1020 |
try:
|
1021 |
print("Step 1: Fetching InciWeb data...")
|
1022 |
inciweb_df = fetch_inciweb_data()
|
1023 |
if inciweb_df.empty:
|
1024 |
-
yield "β Failed to fetch InciWeb data", None, None, None, None, None, None
|
1025 |
return
|
1026 |
print(f"Step 1 SUCCESS: Got {len(inciweb_df)} incidents")
|
1027 |
except Exception as e:
|
1028 |
print(f"Step 1 ERROR: {e}")
|
1029 |
-
yield f"β Error fetching InciWeb data: {str(e)}", None, None, None, None, None, None
|
1030 |
return
|
1031 |
|
1032 |
-
yield f"β
Found {len(inciweb_df)} InciWeb incidents. Getting coordinates...", None, None, None, None, None, None
|
1033 |
|
1034 |
# Get coordinates for sample incidents with error handling
|
1035 |
try:
|
@@ -1041,7 +1111,7 @@ def create_enhanced_wildfire_app():
|
|
1041 |
print(f"Step 2 ERROR: {e}")
|
1042 |
# Continue with the data we have
|
1043 |
|
1044 |
-
yield "π°οΈ Fetching NASA FIRMS fire detection data...", None, None, None, None, None, None
|
1045 |
|
1046 |
# Fetch FIRMS data with error handling
|
1047 |
try:
|
@@ -1050,7 +1120,7 @@ def create_enhanced_wildfire_app():
|
|
1050 |
if firms_df.empty:
|
1051 |
print("Step 3 WARNING: FIRMS data empty")
|
1052 |
# Still useful to show InciWeb data even without FIRMS
|
1053 |
-
yield "β οΈ FIRMS data unavailable, generating basic visualization...", None, None, None, None, None, None
|
1054 |
|
1055 |
# Generate basic map and visualizations without FIRMS data
|
1056 |
try:
|
@@ -1065,23 +1135,26 @@ def create_enhanced_wildfire_app():
|
|
1065 |
inciweb_df.to_csv(csv_file.name, index=False)
|
1066 |
csv_file.close()
|
1067 |
|
|
|
|
|
|
|
1068 |
final_status = f"β
Partial success! Found {len(inciweb_df)} InciWeb incidents (FIRMS data unavailable)"
|
1069 |
-
yield (final_status, map_html, plots[0], inciweb_df, pd.DataFrame(), csv_file.name,
|
1070 |
{"inciweb_df": inciweb_df, "firms_df": pd.DataFrame(), "plots": plots})
|
1071 |
return
|
1072 |
except Exception as e:
|
1073 |
print(f"Error in basic visualization: {e}")
|
1074 |
-
yield f"β Error in basic visualization: {str(e)}", None, None, inciweb_df, pd.DataFrame(), None, None
|
1075 |
return
|
1076 |
|
1077 |
print(f"Step 3 SUCCESS: Got {len(firms_df)} FIRMS hotspots")
|
1078 |
|
1079 |
except Exception as e:
|
1080 |
print(f"Step 3 ERROR: {e}")
|
1081 |
-
yield f"β Error fetching FIRMS data: {str(e)}", None, None, inciweb_df, pd.DataFrame(), None, None
|
1082 |
return
|
1083 |
|
1084 |
-
yield f"β
Found {len(firms_df)} USA fire hotspots. Matching with incidents...", None, None, None, None, None, None
|
1085 |
|
1086 |
# Match FIRMS data to InciWeb incidents with error handling
|
1087 |
try:
|
@@ -1094,15 +1167,15 @@ def create_enhanced_wildfire_app():
|
|
1094 |
enhanced_df = inciweb_df
|
1095 |
print("Using original InciWeb data without FIRMS matching")
|
1096 |
|
1097 |
-
yield "πΊοΈ Generating
|
1098 |
|
1099 |
# Generate map and visualizations with error handling
|
1100 |
try:
|
1101 |
-
print("Step 5: Generating map...")
|
1102 |
map_html = generate_enhanced_map(enhanced_df, firms_df)
|
1103 |
print("Step 5a SUCCESS: Map generated")
|
1104 |
|
1105 |
-
print("Step 5: Generating visualizations...")
|
1106 |
plots = generate_enhanced_visualizations(enhanced_df, firms_df)
|
1107 |
print("Step 5b SUCCESS: Visualizations generated")
|
1108 |
except Exception as e:
|
@@ -1111,40 +1184,59 @@ def create_enhanced_wildfire_app():
|
|
1111 |
map_html = f"<div style='padding: 20px; text-align: center;'>Map generation failed: {str(e)}<br>Data is available in tables below.</div>"
|
1112 |
plots = [px.bar(title=f"Visualization generation failed: {str(e)}")]
|
1113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1114 |
# Prepare export data - create temporary files
|
1115 |
try:
|
1116 |
-
print("Step
|
1117 |
import tempfile
|
1118 |
csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
|
1119 |
enhanced_df.to_csv(csv_file.name, index=False)
|
1120 |
csv_file.close()
|
1121 |
-
print("Step
|
1122 |
except Exception as e:
|
1123 |
-
print(f"Step
|
1124 |
csv_file = None
|
1125 |
|
1126 |
# Calculate final statistics
|
1127 |
try:
|
1128 |
-
active_count = (
|
|
|
1129 |
total_hotspots = len(firms_df) if not firms_df.empty else 0
|
1130 |
coords_count = len(enhanced_df[(enhanced_df['latitude'].notna()) & (enhanced_df['longitude'].notna())])
|
1131 |
|
1132 |
-
final_status = f"
|
1133 |
print(f"FINAL SUCCESS: {final_status}")
|
1134 |
|
1135 |
-
yield (final_status, map_html, plots[0], enhanced_df,
|
1136 |
-
{"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
|
1137 |
except Exception as e:
|
1138 |
print(f"Error calculating final statistics: {e}")
|
1139 |
final_status = "β
Process completed with some errors"
|
1140 |
-
yield (final_status, map_html, plots[0], enhanced_df,
|
1141 |
-
{"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
|
1142 |
|
1143 |
except Exception as e:
|
1144 |
import traceback
|
1145 |
error_details = traceback.format_exc()
|
1146 |
print(f"CRITICAL ERROR in main process: {error_details}")
|
1147 |
-
yield f"β Critical Error: {str(e)}", None, None, None, None, None, None
|
1148 |
|
1149 |
def update_plot(plot_name, state_data):
|
1150 |
"""Update plot based on selection"""
|
@@ -1152,11 +1244,11 @@ def create_enhanced_wildfire_app():
|
|
1152 |
return px.bar(title="No data available")
|
1153 |
|
1154 |
plot_options = [
|
1155 |
-
"
|
1156 |
-
"
|
1157 |
-
"Intensity vs Size
|
1158 |
"Hotspot Detection Timeline",
|
1159 |
-
"
|
1160 |
]
|
1161 |
|
1162 |
try:
|
@@ -1168,7 +1260,7 @@ def create_enhanced_wildfire_app():
|
|
1168 |
# Wire up the interface
|
1169 |
fetch_btn.click(
|
1170 |
fetch_and_process_data,
|
1171 |
-
outputs=[status_text, map_display, plot_display, inciweb_table, firms_table, download_csv, app_state]
|
1172 |
)
|
1173 |
|
1174 |
plot_selector.change(
|
@@ -1181,5 +1273,5 @@ def create_enhanced_wildfire_app():
|
|
1181 |
|
1182 |
# Create and launch the application
|
1183 |
if __name__ == "__main__":
|
1184 |
-
app =
|
1185 |
app.launch(share=True, debug=True)
|
|
|
580 |
print(f"Successfully extracted coordinates for {success_count}/{len(sample_df)} incidents")
|
581 |
return df
|
582 |
|
583 |
+
# Enhanced map generation focusing only on active fires and nearby FIRMS data
|
584 |
def generate_enhanced_map(df, firms_df):
|
585 |
+
"""Generate map showing only active InciWeb incidents and their associated FIRMS hotspots"""
|
586 |
|
587 |
try:
|
588 |
+
print("Starting focused map generation (active fires only)...")
|
589 |
|
590 |
# Create map centered on the US
|
591 |
m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
|
592 |
|
593 |
+
# Filter to only show active incidents (those with nearby FIRMS data)
|
594 |
+
active_incidents = df[df.get('is_active', False) == True].copy()
|
595 |
+
|
596 |
+
if active_incidents.empty:
|
597 |
+
print("No active incidents found - showing basic map")
|
598 |
+
legend_html = '''
|
599 |
+
<div style="position: fixed;
|
600 |
+
bottom: 50px; left: 50px; width: 250px; height: 100px;
|
601 |
+
border:2px solid grey; z-index:9999; font-size:12px;
|
602 |
+
background-color:white; padding: 10px;
|
603 |
+
border-radius: 5px; font-family: Arial;">
|
604 |
+
<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">π₯ No Active Fires Detected</div>
|
605 |
+
<div>No InciWeb incidents have nearby FIRMS hotspots in the last 24 hours.</div>
|
606 |
+
</div>
|
607 |
+
'''
|
608 |
+
map_html = m._repr_html_()
|
609 |
+
return map_html.replace('</body>', legend_html + '</body>')
|
610 |
+
|
611 |
+
print(f"Found {len(active_incidents)} active incidents to display")
|
612 |
+
|
613 |
+
# Collect all FIRMS hotspots that are near active incidents
|
614 |
+
all_nearby_hotspots = []
|
615 |
+
|
616 |
+
for _, incident in active_incidents.iterrows():
|
617 |
+
# Parse hotspot coordinates from stored string
|
618 |
+
hotspot_coords_str = incident.get('hotspot_coords', '')
|
619 |
+
if hotspot_coords_str and hotspot_coords_str != 'None':
|
620 |
+
try:
|
621 |
+
# Safely evaluate the coordinate string
|
622 |
+
import ast
|
623 |
+
hotspot_coords = ast.literal_eval(hotspot_coords_str)
|
624 |
+
all_nearby_hotspots.extend(hotspot_coords)
|
625 |
+
except:
|
626 |
+
continue
|
627 |
+
|
628 |
+
# Add FIRMS heat map layer ONLY for hotspots near active incidents
|
629 |
+
if all_nearby_hotspots:
|
630 |
+
print(f"Adding {len(all_nearby_hotspots)} FIRMS hotspots near active incidents...")
|
631 |
try:
|
|
|
|
|
632 |
heat_data = []
|
633 |
+
for coord in all_nearby_hotspots:
|
|
|
634 |
try:
|
635 |
+
lat, lon, frp = float(coord[0]), float(coord[1]), float(coord[2])
|
|
|
636 |
if -90 <= lat <= 90 and -180 <= lon <= 180: # Valid coordinates
|
637 |
heat_data.append([lat, lon, min(frp, 100)])
|
638 |
+
except (ValueError, TypeError, IndexError):
|
639 |
continue
|
640 |
|
641 |
if heat_data:
|
642 |
HeatMap(
|
643 |
heat_data,
|
644 |
+
name="Active Fire Intensity (NASA FIRMS)",
|
645 |
radius=15,
|
646 |
blur=10,
|
647 |
max_zoom=1,
|
648 |
gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}
|
649 |
).add_to(m)
|
650 |
+
print(f"Added heatmap with {len(heat_data)} hotspots near active incidents")
|
651 |
|
652 |
+
# Add individual FIRMS hotspot markers for active areas only
|
653 |
+
for i, coord in enumerate(all_nearby_hotspots[:100]): # Limit to 100 for performance
|
654 |
try:
|
655 |
+
lat, lon, frp = float(coord[0]), float(coord[1]), float(coord[2])
|
|
|
|
|
|
|
656 |
|
657 |
if -90 <= lat <= 90 and -180 <= lon <= 180:
|
658 |
folium.CircleMarker(
|
659 |
location=[lat, lon],
|
660 |
radius=2 + min(frp / 10, 8),
|
661 |
+
popup=f"π₯ Active Hotspot<br>FRP: {frp:.1f} MW<br>Near active wildfire",
|
662 |
color='red',
|
663 |
fillColor='orange',
|
664 |
fillOpacity=0.7,
|
665 |
weight=1
|
666 |
).add_to(m)
|
667 |
+
except (ValueError, TypeError, IndexError):
|
668 |
continue
|
669 |
|
670 |
except Exception as e:
|
671 |
print(f"Error adding FIRMS data to map: {e}")
|
672 |
|
673 |
+
# Add ONLY active incident markers
|
674 |
+
print(f"Adding {len(active_incidents)} active InciWeb incidents to map...")
|
|
|
|
|
|
|
675 |
|
676 |
+
try:
|
677 |
+
incident_cluster = MarkerCluster(name="Active Wildfire Incidents").add_to(m)
|
678 |
|
679 |
+
for _, row in active_incidents.iterrows():
|
680 |
+
try:
|
681 |
+
lat, lon = float(row['latitude']), float(row['longitude'])
|
682 |
+
|
683 |
+
if not (-90 <= lat <= 90 and -180 <= lon <= 180):
|
684 |
+
continue
|
685 |
+
|
686 |
+
# Determine marker color based on activity level
|
687 |
+
activity_level = row.get('activity_level', 'Unknown')
|
688 |
+
if activity_level == 'Very High':
|
689 |
+
color = 'red'
|
690 |
+
elif activity_level == 'High':
|
691 |
+
color = 'orange'
|
692 |
+
elif activity_level == 'Medium':
|
693 |
+
color = 'yellow'
|
694 |
+
else:
|
695 |
+
color = 'lightred'
|
696 |
+
|
697 |
+
# Create popup content safely
|
698 |
+
name = str(row.get('name', 'Unknown'))
|
699 |
+
incident_type = str(row.get('type', 'N/A'))
|
700 |
+
location = str(row.get('location', 'N/A'))
|
701 |
+
size = row.get('size', 'N/A')
|
702 |
+
updated = str(row.get('updated', 'N/A'))
|
703 |
+
|
704 |
+
firms_hotspots = int(row.get('firms_hotspots', 0))
|
705 |
+
total_frp = float(row.get('total_frp', 0))
|
706 |
+
avg_confidence = float(row.get('avg_confidence', 0))
|
707 |
+
|
708 |
+
popup_content = f"""
|
709 |
+
<div style="width: 300px;">
|
710 |
+
<h4>π₯ {name}</h4>
|
711 |
+
<b>Type:</b> {incident_type}<br>
|
712 |
+
<b>Location:</b> {location}<br>
|
713 |
+
<b>Size:</b> {size} acres<br>
|
714 |
+
<b>Last Updated:</b> {updated}<br>
|
715 |
|
716 |
+
<hr style="margin: 10px 0;">
|
717 |
+
<h5>π‘ Satellite Fire Activity</h5>
|
718 |
+
<b>Status:</b> π΄ ACTIVE (FIRMS confirmed)<br>
|
719 |
+
<b>Activity Level:</b> {activity_level}<br>
|
720 |
+
<b>Hotspots (24h):</b> {firms_hotspots}<br>
|
721 |
+
<b>Total Fire Power:</b> {total_frp:.1f} MW<br>
|
722 |
+
<b>Detection Confidence:</b> {avg_confidence:.1f}%<br>
|
723 |
|
724 |
+
<div style="margin-top: 8px; padding: 5px; background-color: #ffe6e6; border-radius: 3px;">
|
725 |
+
<small><b>π°οΈ Real-time confirmed:</b> This fire has active satellite hotspots detected in the last 24 hours</small>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
726 |
</div>
|
727 |
+
</div>
|
728 |
+
"""
|
729 |
+
|
730 |
+
folium.Marker(
|
731 |
+
location=[lat, lon],
|
732 |
+
popup=folium.Popup(popup_content, max_width=350),
|
733 |
+
icon=folium.Icon(color=color, icon='fire', prefix='fa')
|
734 |
+
).add_to(incident_cluster)
|
735 |
+
|
736 |
+
except Exception as e:
|
737 |
+
print(f"Error adding active incident marker: {e}")
|
738 |
+
continue
|
739 |
+
|
740 |
+
except Exception as e:
|
741 |
+
print(f"Error creating active incident markers: {e}")
|
|
|
|
|
742 |
|
743 |
+
# Add focused legend for active fires only
|
744 |
+
total_active = len(active_incidents)
|
745 |
+
total_hotspots = len(all_nearby_hotspots)
|
746 |
|
747 |
legend_html = f'''
|
748 |
<div style="position: fixed;
|
749 |
+
bottom: 50px; left: 50px; width: 280px; height: 280px;
|
750 |
border:2px solid grey; z-index:9999; font-size:12px;
|
751 |
background-color:white; padding: 10px;
|
752 |
border-radius: 5px; font-family: Arial;">
|
753 |
+
<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">π₯ Active Wildfire Detection</div>
|
754 |
|
755 |
+
<div style="margin-bottom: 8px;"><b>Fire Activity Levels:</b></div>
|
756 |
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
757 |
<div style="background-color: red; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
758 |
<div>Very High Activity</div>
|
|
|
765 |
<div style="background-color: yellow; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
766 |
<div>Medium Activity</div>
|
767 |
</div>
|
768 |
+
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
769 |
<div style="background-color: lightcoral; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
770 |
<div>Low Activity</div>
|
771 |
</div>
|
|
|
|
|
|
|
|
|
772 |
|
773 |
+
<div style="margin-bottom: 5px;"><b>Satellite Data:</b></div>
|
774 |
+
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
775 |
<div style="background-color: orange; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
776 |
+
<div>NASA FIRMS Hotspots</div>
|
777 |
</div>
|
|
|
778 |
|
779 |
<div style="font-size: 11px; margin-top: 10px; padding-top: 5px; border-top: 1px solid #ccc;">
|
780 |
+
<b>π― Filtered Results:</b><br>
|
781 |
+
π₯ Active Fires: {total_active}<br>
|
782 |
+
π‘ Satellite Hotspots: {total_hotspots}<br>
|
783 |
+
|
784 |
+
<div style="margin-top: 5px; font-style: italic; color: #666;">
|
785 |
+
Only showing incidents with recent satellite fire detection
|
786 |
+
</div>
|
787 |
</div>
|
788 |
</div>
|
789 |
'''
|
|
|
798 |
try:
|
799 |
map_html = m._repr_html_()
|
800 |
map_with_legend = map_html.replace('</body>', legend_html + '</body>')
|
801 |
+
print(f"Map generation completed successfully - showing {total_active} active fires")
|
802 |
return map_with_legend
|
803 |
except Exception as e:
|
804 |
print(f"Error generating final map HTML: {e}")
|
805 |
return f"<div style='padding: 20px; text-align: center;'>Map generation error: {str(e)}</div>"
|
806 |
|
807 |
except Exception as e:
|
808 |
+
print(f"Critical error in focused map generation: {e}")
|
809 |
import traceback
|
810 |
traceback.print_exc()
|
811 |
return f"<div style='padding: 20px; text-align: center;'>Critical map error: {str(e)}</div>"
|
812 |
|
813 |
+
# Enhanced visualization functions focusing on active fires only
|
814 |
def generate_enhanced_visualizations(df, firms_df):
|
815 |
+
"""Generate enhanced visualizations focusing only on active fires with FIRMS data integration"""
|
816 |
figures = []
|
817 |
|
818 |
try:
|
819 |
+
print("Starting focused visualization generation (active fires only)...")
|
820 |
|
821 |
if df.empty:
|
822 |
print("Warning: Empty dataframe for visualizations")
|
823 |
return [px.bar(title="No data available")]
|
824 |
|
825 |
+
# Filter to only active incidents for most visualizations
|
826 |
+
active_df = df[df.get('is_active', False) == True].copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
827 |
|
828 |
+
# 1. Active Fire Activity Levels (only active fires)
|
829 |
try:
|
830 |
+
if not active_df.empty and 'activity_level' in active_df.columns:
|
831 |
+
activity_levels = active_df['activity_level'].value_counts().reset_index()
|
832 |
+
activity_levels.columns = ['activity_level', 'count']
|
833 |
|
834 |
+
# Define order and colors
|
835 |
+
level_order = ['Very High', 'High', 'Medium', 'Low', 'Minimal']
|
836 |
+
color_map = {
|
837 |
+
'Very High': 'darkred',
|
838 |
+
'High': 'red',
|
839 |
+
'Medium': 'orange',
|
840 |
+
'Low': 'yellow',
|
841 |
+
'Minimal': 'lightblue'
|
842 |
+
}
|
843 |
+
|
844 |
+
fig1 = px.bar(
|
845 |
+
activity_levels,
|
846 |
+
x='activity_level',
|
847 |
y='count',
|
848 |
+
title="π₯ Active Fire Intensity Levels (NASA FIRMS Confirmed)",
|
849 |
+
labels={'activity_level': 'Fire Activity Level', 'count': 'Number of Active Fires'},
|
850 |
+
color='activity_level',
|
851 |
+
color_discrete_map=color_map,
|
852 |
+
category_orders={'activity_level': level_order}
|
853 |
+
)
|
854 |
+
fig1.update_layout(
|
855 |
+
title_font_size=16,
|
856 |
+
showlegend=False
|
857 |
)
|
858 |
else:
|
859 |
+
fig1 = px.bar(title="No active fires detected with FIRMS data")
|
860 |
except Exception as e:
|
861 |
+
print(f"Error creating activity level chart: {e}")
|
862 |
+
fig1 = px.bar(title=f"Activity level error: {str(e)}")
|
863 |
+
figures.append(fig1)
|
864 |
|
865 |
+
# 2. Active Fires by State (only active fires)
|
866 |
try:
|
867 |
+
if not active_df.empty and 'state' in active_df.columns:
|
868 |
+
state_counts = active_df['state'].value_counts().reset_index()
|
869 |
state_counts.columns = ['state_name', 'count']
|
870 |
|
871 |
+
fig2 = px.bar(
|
872 |
state_counts,
|
873 |
x='state_name',
|
874 |
y='count',
|
875 |
+
title="πΊοΈ Active Fires by State (FIRMS Confirmed)",
|
876 |
+
labels={'state_name': 'State', 'count': 'Number of Active Fires'},
|
877 |
+
color='count',
|
878 |
+
color_continuous_scale='Reds'
|
879 |
+
)
|
880 |
+
fig2.update_layout(
|
881 |
+
title_font_size=16,
|
882 |
+
showlegend=False
|
883 |
)
|
884 |
else:
|
885 |
+
fig2 = px.bar(title="No active fires by state data available")
|
886 |
except Exception as e:
|
887 |
print(f"Error creating state distribution chart: {e}")
|
888 |
+
fig2 = px.bar(title=f"State distribution error: {str(e)}")
|
889 |
+
figures.append(fig2)
|
890 |
|
891 |
+
# 3. Fire Intensity vs Size Scatter (only active fires)
|
892 |
try:
|
893 |
+
if not active_df.empty and 'total_frp' in active_df.columns and 'size' in active_df.columns:
|
894 |
+
# Filter to fires with both size and FRP data
|
895 |
+
scatter_df = active_df[
|
896 |
+
(active_df['total_frp'] > 0) &
|
897 |
+
(active_df['size'].notna()) &
|
898 |
+
(active_df['size'] > 0)
|
899 |
+
].copy()
|
900 |
|
901 |
+
if not scatter_df.empty:
|
902 |
+
fig3 = px.scatter(
|
903 |
+
scatter_df,
|
904 |
+
x='size',
|
905 |
+
y='total_frp',
|
906 |
+
size='firms_hotspots',
|
907 |
+
color='activity_level',
|
908 |
+
hover_data=['name', 'state', 'firms_hotspots'],
|
909 |
+
title="π₯ Fire Intensity vs Size (Active Fires Only)",
|
910 |
+
labels={
|
911 |
+
'size': 'Fire Size (acres)',
|
912 |
+
'total_frp': 'Satellite Fire Power (MW)',
|
913 |
+
'firms_hotspots': 'Hotspot Count'
|
914 |
+
},
|
915 |
+
color_discrete_map={
|
916 |
+
'Very High': 'darkred',
|
917 |
+
'High': 'red',
|
918 |
+
'Medium': 'orange',
|
919 |
+
'Low': 'yellow'
|
920 |
+
}
|
921 |
+
)
|
922 |
+
fig3.update_layout(
|
923 |
+
title_font_size=16,
|
924 |
+
xaxis_type="log",
|
925 |
+
yaxis_type="log"
|
926 |
)
|
|
|
927 |
else:
|
928 |
+
fig3 = px.bar(title="No active fires with size and intensity data")
|
929 |
+
else:
|
930 |
+
fig3 = px.bar(title="Fire intensity vs size data not available")
|
931 |
+
except Exception as e:
|
932 |
+
print(f"Error creating scatter plot: {e}")
|
933 |
+
fig3 = px.bar(title=f"Scatter plot error: {str(e)}")
|
934 |
+
figures.append(fig3)
|
935 |
+
|
936 |
+
# 4. FIRMS Hotspot Detection Timeline (only hotspots near active incidents)
|
937 |
+
try:
|
938 |
+
if not firms_df.empty and 'datetime' in firms_df.columns and not active_df.empty:
|
939 |
+
# Get all hotspots that are near active incidents
|
940 |
+
all_nearby_hotspots_coords = []
|
941 |
+
for _, incident in active_df.iterrows():
|
942 |
+
hotspot_coords_str = incident.get('hotspot_coords', '')
|
943 |
+
if hotspot_coords_str and hotspot_coords_str != 'None':
|
944 |
+
try:
|
945 |
+
import ast
|
946 |
+
hotspot_coords = ast.literal_eval(hotspot_coords_str)
|
947 |
+
all_nearby_hotspots_coords.extend(hotspot_coords)
|
948 |
+
except:
|
949 |
+
continue
|
950 |
+
|
951 |
+
if all_nearby_hotspots_coords:
|
952 |
+
# Create timeline based on FIRMS data filtered to active areas
|
953 |
+
firms_copy = firms_df.copy()
|
954 |
+
firms_copy['hour'] = pd.to_datetime(firms_copy['datetime']).dt.floor('H')
|
955 |
+
hourly_detections = firms_copy.groupby('hour').size().reset_index(name='detections')
|
956 |
+
|
957 |
+
if not hourly_detections.empty:
|
958 |
+
fig4 = px.line(
|
959 |
+
hourly_detections,
|
960 |
+
x='hour',
|
961 |
+
y='detections',
|
962 |
+
title="π Active Fire Hotspot Detections Over Time (Near Active Incidents)",
|
963 |
+
labels={'hour': 'Time', 'detections': 'Hotspots Detected'},
|
964 |
+
markers=True
|
965 |
+
)
|
966 |
+
fig4.update_traces(line_color='red', marker_color='orange')
|
967 |
+
fig4.update_layout(title_font_size=16)
|
968 |
+
else:
|
969 |
+
fig4 = px.bar(title="No temporal FIRMS data available")
|
970 |
+
else:
|
971 |
+
fig4 = px.bar(title="No hotspots near active incidents found")
|
972 |
else:
|
973 |
fig4 = px.bar(title="FIRMS temporal data not available")
|
974 |
except Exception as e:
|
975 |
+
print(f"Error creating timeline chart: {e}")
|
976 |
+
fig4 = px.bar(title=f"Timeline error: {str(e)}")
|
977 |
figures.append(fig4)
|
978 |
|
979 |
+
# 5. Active vs Inactive Fire Summary
|
980 |
try:
|
981 |
+
active_count = len(active_df)
|
982 |
+
inactive_count = len(df) - active_count
|
983 |
+
|
984 |
+
if active_count > 0 or inactive_count > 0:
|
985 |
+
summary_data = pd.DataFrame({
|
986 |
+
'status': ['π₯ Active (FIRMS Confirmed)', 'β« Inactive/No Data'],
|
987 |
+
'count': [active_count, inactive_count]
|
988 |
+
})
|
989 |
+
|
990 |
+
fig5 = px.pie(
|
991 |
+
summary_data,
|
992 |
+
values='count',
|
993 |
+
names='status',
|
994 |
+
title="π Fire Detection Summary (InciWeb vs FIRMS)",
|
995 |
+
color_discrete_map={
|
996 |
+
'π₯ Active (FIRMS Confirmed)': 'red',
|
997 |
+
'β« Inactive/No Data': 'gray'
|
998 |
}
|
999 |
+
)
|
1000 |
+
fig5.update_traces(textinfo='label+percent+value')
|
1001 |
+
fig5.update_layout(title_font_size=16)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1002 |
else:
|
1003 |
+
fig5 = px.bar(title="No fire status data available")
|
1004 |
except Exception as e:
|
1005 |
+
print(f"Error creating summary chart: {e}")
|
1006 |
+
fig5 = px.bar(title=f"Summary error: {str(e)}")
|
1007 |
figures.append(fig5)
|
1008 |
|
1009 |
+
print(f"Generated {len(figures)} focused visualizations for {len(active_df)} active fires")
|
1010 |
return figures
|
1011 |
|
1012 |
except Exception as e:
|
1013 |
+
print(f"Critical error in focused visualization generation: {e}")
|
1014 |
import traceback
|
1015 |
traceback.print_exc()
|
1016 |
return [px.bar(title=f"Critical visualization error: {str(e)}")]
|
1017 |
|
1018 |
# Main application function
|
1019 |
+
def create_focused_wildfire_app():
|
1020 |
+
"""Create the focused active wildfire Gradio application"""
|
1021 |
|
1022 |
+
with gr.Blocks(title="Focused Active Wildfire Tracker", theme=gr.themes.Soft()) as app:
|
1023 |
gr.Markdown("""
|
1024 |
+
# π₯ Focused Active Wildfire Tracker
|
1025 |
## InciWeb Incidents + NASA FIRMS Real-Time Fire Detection
|
1026 |
|
1027 |
+
This application identifies **currently active wildfires** by combining official incident reports from InciWeb with real-time satellite fire detection data from NASA FIRMS:
|
1028 |
+
|
1029 |
+
### π― **What You'll See:**
|
1030 |
+
- **π₯ Active Fires Only**: InciWeb incidents that have nearby satellite-detected hotspots (confirmed burning)
|
1031 |
+
- **π‘ Real-Time Data**: NASA FIRMS satellite fire detection from the last 24 hours
|
1032 |
+
- **π°οΈ Fire Intensity**: Fire Radiative Power (FRP) measurements showing fire strength
|
1033 |
+
- **πΊοΈ Focused Map**: Clean visualization showing only confirmed active wildfires and their satellite data
|
1034 |
+
|
1035 |
+
### π« **What's Filtered Out:**
|
1036 |
+
- InciWeb incidents without recent satellite fire activity (likely contained/inactive)
|
1037 |
+
- Random FIRMS hotspots not near known incidents
|
1038 |
+
- Outdated or inactive fire reports
|
1039 |
+
|
1040 |
+
**Result: A precise view of what's actually burning right now!** π₯π°οΈ
|
1041 |
""")
|
1042 |
|
1043 |
with gr.Row():
|
1044 |
+
fetch_btn = gr.Button("π Fetch Active Wildfire Data (InciWeb + NASA FIRMS)", variant="primary", size="lg")
|
1045 |
+
status_text = gr.Textbox(label="Status", interactive=False, value="Ready to fetch active wildfire data...")
|
1046 |
|
1047 |
with gr.Tabs():
|
1048 |
with gr.TabItem("πΊοΈ Enhanced Map"):
|
|
|
1052 |
with gr.Row():
|
1053 |
plot_selector = gr.Dropdown(
|
1054 |
choices=[
|
1055 |
+
"Active Fire Intensity Levels",
|
1056 |
+
"Active Fires by State",
|
1057 |
+
"Fire Intensity vs Size",
|
1058 |
"Hotspot Detection Timeline",
|
1059 |
+
"Active vs Inactive Summary"
|
1060 |
],
|
1061 |
label="Select Visualization",
|
1062 |
+
value="Active Fire Intensity Levels"
|
1063 |
)
|
1064 |
+
plot_display = gr.Plot(label="Enhanced Analytics (Active Fires Focus)")
|
1065 |
|
1066 |
with gr.TabItem("π Data Tables"):
|
1067 |
with gr.Tabs():
|
1068 |
+
with gr.TabItem("π₯ Active Fires"):
|
1069 |
+
active_fires_table = gr.Dataframe(label="Active Fires (FIRMS Confirmed)")
|
1070 |
+
with gr.TabItem("π All InciWeb Incidents"):
|
1071 |
+
inciweb_table = gr.Dataframe(label="All InciWeb Incidents")
|
1072 |
+
with gr.TabItem("π°οΈ NASA FIRMS Data"):
|
1073 |
+
firms_table = gr.Dataframe(label="NASA FIRMS Fire Hotspots (Near Active Incidents)")
|
1074 |
|
1075 |
with gr.TabItem("π Export Data"):
|
1076 |
gr.Markdown("### Download Enhanced Dataset")
|
|
|
1084 |
def fetch_and_process_data():
|
1085 |
"""Main data processing function with comprehensive error handling and debugging"""
|
1086 |
try:
|
1087 |
+
yield "π‘ Fetching InciWeb incident data...", None, None, None, None, None, None, None
|
1088 |
|
1089 |
# Fetch InciWeb data with error handling
|
1090 |
try:
|
1091 |
print("Step 1: Fetching InciWeb data...")
|
1092 |
inciweb_df = fetch_inciweb_data()
|
1093 |
if inciweb_df.empty:
|
1094 |
+
yield "β Failed to fetch InciWeb data", None, None, None, None, None, None, None
|
1095 |
return
|
1096 |
print(f"Step 1 SUCCESS: Got {len(inciweb_df)} incidents")
|
1097 |
except Exception as e:
|
1098 |
print(f"Step 1 ERROR: {e}")
|
1099 |
+
yield f"β Error fetching InciWeb data: {str(e)}", None, None, None, None, None, None, None
|
1100 |
return
|
1101 |
|
1102 |
+
yield f"β
Found {len(inciweb_df)} InciWeb incidents. Getting coordinates...", None, None, None, None, None, None, None
|
1103 |
|
1104 |
# Get coordinates for sample incidents with error handling
|
1105 |
try:
|
|
|
1111 |
print(f"Step 2 ERROR: {e}")
|
1112 |
# Continue with the data we have
|
1113 |
|
1114 |
+
yield "π°οΈ Fetching NASA FIRMS fire detection data...", None, None, None, None, None, None, None
|
1115 |
|
1116 |
# Fetch FIRMS data with error handling
|
1117 |
try:
|
|
|
1120 |
if firms_df.empty:
|
1121 |
print("Step 3 WARNING: FIRMS data empty")
|
1122 |
# Still useful to show InciWeb data even without FIRMS
|
1123 |
+
yield "β οΈ FIRMS data unavailable, generating basic visualization...", None, None, None, None, None, None, None
|
1124 |
|
1125 |
# Generate basic map and visualizations without FIRMS data
|
1126 |
try:
|
|
|
1135 |
inciweb_df.to_csv(csv_file.name, index=False)
|
1136 |
csv_file.close()
|
1137 |
|
1138 |
+
# Create empty active fires table
|
1139 |
+
active_fires_df = pd.DataFrame()
|
1140 |
+
|
1141 |
final_status = f"β
Partial success! Found {len(inciweb_df)} InciWeb incidents (FIRMS data unavailable)"
|
1142 |
+
yield (final_status, map_html, plots[0], active_fires_df, inciweb_df, pd.DataFrame(), csv_file.name,
|
1143 |
{"inciweb_df": inciweb_df, "firms_df": pd.DataFrame(), "plots": plots})
|
1144 |
return
|
1145 |
except Exception as e:
|
1146 |
print(f"Error in basic visualization: {e}")
|
1147 |
+
yield f"β Error in basic visualization: {str(e)}", None, None, inciweb_df, None, pd.DataFrame(), None, None
|
1148 |
return
|
1149 |
|
1150 |
print(f"Step 3 SUCCESS: Got {len(firms_df)} FIRMS hotspots")
|
1151 |
|
1152 |
except Exception as e:
|
1153 |
print(f"Step 3 ERROR: {e}")
|
1154 |
+
yield f"β Error fetching FIRMS data: {str(e)}", None, None, inciweb_df, None, pd.DataFrame(), None, None
|
1155 |
return
|
1156 |
|
1157 |
+
yield f"β
Found {len(firms_df)} USA fire hotspots. Matching with incidents...", None, None, None, None, None, None, None
|
1158 |
|
1159 |
# Match FIRMS data to InciWeb incidents with error handling
|
1160 |
try:
|
|
|
1167 |
enhanced_df = inciweb_df
|
1168 |
print("Using original InciWeb data without FIRMS matching")
|
1169 |
|
1170 |
+
yield "πΊοΈ Generating focused map and analytics (active fires only)...", None, None, None, None, None, None, None
|
1171 |
|
1172 |
# Generate map and visualizations with error handling
|
1173 |
try:
|
1174 |
+
print("Step 5: Generating focused map...")
|
1175 |
map_html = generate_enhanced_map(enhanced_df, firms_df)
|
1176 |
print("Step 5a SUCCESS: Map generated")
|
1177 |
|
1178 |
+
print("Step 5: Generating focused visualizations...")
|
1179 |
plots = generate_enhanced_visualizations(enhanced_df, firms_df)
|
1180 |
print("Step 5b SUCCESS: Visualizations generated")
|
1181 |
except Exception as e:
|
|
|
1184 |
map_html = f"<div style='padding: 20px; text-align: center;'>Map generation failed: {str(e)}<br>Data is available in tables below.</div>"
|
1185 |
plots = [px.bar(title=f"Visualization generation failed: {str(e)}")]
|
1186 |
|
1187 |
+
# Create separate tables for active vs all incidents
|
1188 |
+
try:
|
1189 |
+
print("Step 6: Creating data tables...")
|
1190 |
+
# Active fires table (only incidents with FIRMS activity)
|
1191 |
+
active_fires_df = enhanced_df[enhanced_df.get('is_active', False) == True].copy()
|
1192 |
+
|
1193 |
+
# Filter FIRMS data to only hotspots near active incidents
|
1194 |
+
firms_near_active = pd.DataFrame()
|
1195 |
+
if not active_fires_df.empty and not firms_df.empty:
|
1196 |
+
# This is a simplified version - in a real implementation you'd filter more precisely
|
1197 |
+
firms_near_active = firms_df.head(100) # Limit for display
|
1198 |
+
|
1199 |
+
print(f"Step 6 SUCCESS: {len(active_fires_df)} active fires, {len(firms_near_active)} nearby FIRMS hotspots")
|
1200 |
+
except Exception as e:
|
1201 |
+
print(f"Step 6 ERROR: {e}")
|
1202 |
+
active_fires_df = pd.DataFrame()
|
1203 |
+
firms_near_active = pd.DataFrame()
|
1204 |
+
|
1205 |
# Prepare export data - create temporary files
|
1206 |
try:
|
1207 |
+
print("Step 7: Creating CSV export...")
|
1208 |
import tempfile
|
1209 |
csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
|
1210 |
enhanced_df.to_csv(csv_file.name, index=False)
|
1211 |
csv_file.close()
|
1212 |
+
print("Step 7 SUCCESS: CSV created")
|
1213 |
except Exception as e:
|
1214 |
+
print(f"Step 7 ERROR: {e}")
|
1215 |
csv_file = None
|
1216 |
|
1217 |
# Calculate final statistics
|
1218 |
try:
|
1219 |
+
active_count = len(active_fires_df)
|
1220 |
+
total_incidents = len(enhanced_df)
|
1221 |
total_hotspots = len(firms_df) if not firms_df.empty else 0
|
1222 |
coords_count = len(enhanced_df[(enhanced_df['latitude'].notna()) & (enhanced_df['longitude'].notna())])
|
1223 |
|
1224 |
+
final_status = f"π― Focused Results: {active_count} active fires detected with satellite confirmation"
|
1225 |
print(f"FINAL SUCCESS: {final_status}")
|
1226 |
|
1227 |
+
yield (final_status, map_html, plots[0], active_fires_df, enhanced_df, firms_near_active, csv_file.name if csv_file else None,
|
1228 |
+
{"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots, "active_df": active_fires_df})
|
1229 |
except Exception as e:
|
1230 |
print(f"Error calculating final statistics: {e}")
|
1231 |
final_status = "β
Process completed with some errors"
|
1232 |
+
yield (final_status, map_html, plots[0], active_fires_df, enhanced_df, firms_near_active, csv_file.name if csv_file else None,
|
1233 |
+
{"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots, "active_df": active_fires_df})
|
1234 |
|
1235 |
except Exception as e:
|
1236 |
import traceback
|
1237 |
error_details = traceback.format_exc()
|
1238 |
print(f"CRITICAL ERROR in main process: {error_details}")
|
1239 |
+
yield f"β Critical Error: {str(e)}", None, None, None, None, None, None, None
|
1240 |
|
1241 |
def update_plot(plot_name, state_data):
|
1242 |
"""Update plot based on selection"""
|
|
|
1244 |
return px.bar(title="No data available")
|
1245 |
|
1246 |
plot_options = [
|
1247 |
+
"Active Fire Intensity Levels",
|
1248 |
+
"Active Fires by State",
|
1249 |
+
"Fire Intensity vs Size",
|
1250 |
"Hotspot Detection Timeline",
|
1251 |
+
"Active vs Inactive Summary"
|
1252 |
]
|
1253 |
|
1254 |
try:
|
|
|
1260 |
# Wire up the interface
|
1261 |
fetch_btn.click(
|
1262 |
fetch_and_process_data,
|
1263 |
+
outputs=[status_text, map_display, plot_display, active_fires_table, inciweb_table, firms_table, download_csv, app_state]
|
1264 |
)
|
1265 |
|
1266 |
plot_selector.change(
|
|
|
1273 |
|
1274 |
# Create and launch the application
|
1275 |
if __name__ == "__main__":
|
1276 |
+
app = create_focused_wildfire_app()
|
1277 |
app.launch(share=True, debug=True)
|