Update app.py
Browse files
app.py
CHANGED
@@ -135,98 +135,110 @@ def fetch_firms_data():
|
|
135 |
def match_firms_to_inciweb(inciweb_df, firms_df, max_distance_km=50):
|
136 |
"""
|
137 |
Match FIRMS hotspots to InciWeb incidents based on geographic proximity
|
138 |
-
|
139 |
-
Args:
|
140 |
-
inciweb_df: DataFrame with InciWeb incident data (must have latitude/longitude)
|
141 |
-
firms_df: DataFrame with FIRMS hotspot data
|
142 |
-
max_distance_km: Maximum distance in km to consider a match
|
143 |
-
|
144 |
-
Returns:
|
145 |
-
Enhanced inciweb_df with FIRMS data and activity status
|
146 |
"""
|
147 |
if firms_df.empty or inciweb_df.empty:
|
|
|
148 |
return inciweb_df
|
149 |
|
150 |
-
|
151 |
-
|
152 |
-
# Initialize new columns
|
153 |
-
inciweb_df = inciweb_df.copy()
|
154 |
-
inciweb_df['firms_hotspots'] = 0
|
155 |
-
inciweb_df['total_frp'] = 0.0 # Fire Radiative Power
|
156 |
-
inciweb_df['avg_confidence'] = 0.0
|
157 |
-
inciweb_df['latest_hotspot'] = None
|
158 |
-
inciweb_df['is_active'] = False
|
159 |
-
inciweb_df['hotspot_coords'] = None
|
160 |
-
inciweb_df['activity_level'] = 'Unknown'
|
161 |
-
|
162 |
-
# Only process incidents that have coordinates
|
163 |
-
incidents_with_coords = inciweb_df[
|
164 |
-
(inciweb_df['latitude'].notna()) & (inciweb_df['longitude'].notna())
|
165 |
-
].copy()
|
166 |
-
|
167 |
-
print(f"Processing {len(incidents_with_coords)} incidents with coordinates...")
|
168 |
-
|
169 |
-
for idx, incident in incidents_with_coords.iterrows():
|
170 |
-
incident_coords = (incident['latitude'], incident['longitude'])
|
171 |
|
172 |
-
#
|
173 |
-
|
174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
|
176 |
-
|
177 |
-
|
178 |
-
|
|
|
|
|
|
|
|
|
|
|
179 |
try:
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
except Exception as e:
|
185 |
-
|
|
|
186 |
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
activity_level = 'Minimal'
|
207 |
-
|
208 |
-
# Update the incident data
|
209 |
-
inciweb_df.at[idx, 'firms_hotspots'] = num_hotspots
|
210 |
-
inciweb_df.at[idx, 'total_frp'] = total_frp
|
211 |
-
inciweb_df.at[idx, 'avg_confidence'] = avg_confidence
|
212 |
-
inciweb_df.at[idx, 'latest_hotspot'] = latest_hotspot
|
213 |
-
inciweb_df.at[idx, 'is_active'] = True
|
214 |
-
inciweb_df.at[idx, 'activity_level'] = activity_level
|
215 |
-
|
216 |
-
# Store hotspot coordinates for visualization
|
217 |
-
hotspot_coords = [(hs['latitude'], hs['longitude'], hs.get('frp', 1))
|
218 |
-
for hs in matched_hotspots]
|
219 |
-
inciweb_df.at[idx, 'hotspot_coords'] = hotspot_coords
|
220 |
-
|
221 |
-
print(f" {incident['name']}: {num_hotspots} hotspots, {total_frp:.1f} FRP, {activity_level} activity")
|
222 |
-
|
223 |
-
# Mark incidents without recent hotspots as potentially inactive
|
224 |
-
active_count = (inciweb_df['is_active'] == True).sum()
|
225 |
-
total_with_coords = len(incidents_with_coords)
|
226 |
-
|
227 |
-
print(f"Found {active_count} active incidents out of {total_with_coords} with coordinates")
|
228 |
-
|
229 |
-
return inciweb_df
|
230 |
|
231 |
# Function to scrape InciWeb data from the accessible view page
|
232 |
def fetch_inciweb_data():
|
@@ -284,9 +296,20 @@ def fetch_inciweb_data():
|
|
284 |
size_cell = row_cells[3] if len(row_cells) > 3 else None
|
285 |
if size_cell:
|
286 |
size_text = size_cell.text.strip()
|
287 |
-
|
288 |
-
|
289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
else:
|
291 |
incident["size"] = None
|
292 |
|
@@ -301,12 +324,28 @@ def fetch_inciweb_data():
|
|
301 |
|
302 |
df = pd.DataFrame(incidents)
|
303 |
|
304 |
-
# Ensure all expected columns exist
|
305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
306 |
if col not in df.columns:
|
307 |
-
df[col] =
|
308 |
|
309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
310 |
|
311 |
print(f"Fetched {len(df)} incidents")
|
312 |
return df
|
@@ -498,299 +537,379 @@ def add_coordinates_to_incidents(df, max_incidents=30):
|
|
498 |
|
499 |
# Enhanced map generation with FIRMS data
|
500 |
def generate_enhanced_map(df, firms_df):
|
501 |
-
"""Generate map with both InciWeb incidents and FIRMS hotspots"""
|
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 |
active_incidents = 0
|
545 |
inactive_incidents = 0
|
546 |
|
547 |
-
|
548 |
-
|
549 |
|
550 |
-
#
|
551 |
-
|
552 |
-
|
553 |
-
activity_level = row.get('activity_level', 'Unknown')
|
554 |
-
if activity_level == 'Very High':
|
555 |
-
color = 'red'
|
556 |
-
icon = 'fire'
|
557 |
-
elif activity_level == 'High':
|
558 |
-
color = 'orange'
|
559 |
-
icon = 'fire'
|
560 |
-
elif activity_level == 'Medium':
|
561 |
-
color = 'yellow'
|
562 |
-
icon = 'fire'
|
563 |
-
else:
|
564 |
-
color = 'lightred'
|
565 |
-
icon = 'fire'
|
566 |
-
else:
|
567 |
-
inactive_incidents += 1
|
568 |
-
color = 'gray'
|
569 |
-
icon = 'pause'
|
570 |
-
|
571 |
-
# Create detailed popup
|
572 |
-
popup_content = f"""
|
573 |
-
<div style="width: 300px;">
|
574 |
-
<h4>{row.get('name', 'Unknown')}</h4>
|
575 |
-
<b>Type:</b> {row.get('type', 'N/A')}<br>
|
576 |
-
<b>Location:</b> {row.get('location', 'N/A')}<br>
|
577 |
-
<b>Size:</b> {row.get('size', 'N/A')} acres<br>
|
578 |
-
<b>Last Updated:</b> {row.get('updated', 'N/A')}<br>
|
579 |
-
|
580 |
-
<hr style="margin: 10px 0;">
|
581 |
-
<h5>π₯ Fire Activity (NASA FIRMS)</h5>
|
582 |
-
<b>Status:</b> {'π΄ ACTIVE' if row.get('is_active', False) else 'β« Inactive'}<br>
|
583 |
-
<b>Activity Level:</b> {row.get('activity_level', 'Unknown')}<br>
|
584 |
-
<b>Hotspots (24h):</b> {row.get('firms_hotspots', 0)}<br>
|
585 |
-
<b>Total Fire Power:</b> {row.get('total_frp', 0):.1f} MW<br>
|
586 |
-
<b>Avg Confidence:</b> {row.get('avg_confidence', 0):.1f}%<br>
|
587 |
|
588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
589 |
</div>
|
590 |
-
"""
|
591 |
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
|
|
597 |
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
radius=3 + min(coord[2] / 20, 10), # Size based on FRP
|
607 |
-
popup=f"π₯ Hotspot<br>FRP: {coord[2]:.1f} MW",
|
608 |
-
color='red',
|
609 |
-
fillColor='orange',
|
610 |
-
fillOpacity=0.7
|
611 |
-
).add_to(m)
|
612 |
-
else:
|
613 |
-
print("No InciWeb incidents have coordinates, showing FIRMS data only")
|
614 |
-
active_incidents = 0
|
615 |
-
inactive_incidents = len(df)
|
616 |
-
|
617 |
-
# Add custom legend
|
618 |
-
total_hotspots = len(firms_df) if not firms_df.empty else 0
|
619 |
-
total_incidents = len(df)
|
620 |
-
|
621 |
-
legend_html = f'''
|
622 |
-
<div style="position: fixed;
|
623 |
-
bottom: 50px; left: 50px; width: 250px; height: 320px;
|
624 |
-
border:2px solid grey; z-index:9999; font-size:12px;
|
625 |
-
background-color:white; padding: 10px;
|
626 |
-
border-radius: 5px; font-family: Arial;">
|
627 |
-
<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">π₯ Wildfire Activity Status</div>
|
628 |
-
|
629 |
-
<div style="margin-bottom: 8px;"><b>InciWeb Incidents:</b></div>
|
630 |
-
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
631 |
-
<div style="background-color: red; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
632 |
-
<div>Very High Activity</div>
|
633 |
-
</div>
|
634 |
-
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
635 |
-
<div style="background-color: orange; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
636 |
-
<div>High Activity</div>
|
637 |
-
</div>
|
638 |
-
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
639 |
-
<div style="background-color: yellow; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
640 |
-
<div>Medium Activity</div>
|
641 |
-
</div>
|
642 |
-
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
643 |
-
<div style="background-color: lightcoral; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
644 |
-
<div>Low Activity</div>
|
645 |
-
</div>
|
646 |
-
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
647 |
-
<div style="background-color: gray; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
648 |
-
<div>Inactive/No Data</div>
|
649 |
</div>
|
|
|
650 |
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
# Get map HTML and add legend
|
673 |
-
map_html = m._repr_html_()
|
674 |
-
map_with_legend = map_html.replace('</body>', legend_html + '</body>')
|
675 |
-
|
676 |
-
return map_with_legend
|
677 |
|
678 |
# Enhanced visualization functions
|
679 |
def generate_enhanced_visualizations(df, firms_df):
|
680 |
-
"""Generate enhanced visualizations with FIRMS data integration"""
|
681 |
figures = []
|
682 |
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
)
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
)
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
794 |
|
795 |
# Main application function
|
796 |
def create_enhanced_wildfire_app():
|
@@ -848,58 +967,139 @@ def create_enhanced_wildfire_app():
|
|
848 |
app_state = gr.State({})
|
849 |
|
850 |
def fetch_and_process_data():
|
851 |
-
"""Main data processing function"""
|
852 |
try:
|
853 |
yield "π‘ Fetching InciWeb incident data...", None, None, None, None, None, None
|
854 |
|
855 |
-
# Fetch InciWeb data
|
856 |
-
|
857 |
-
|
858 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
859 |
return
|
860 |
|
861 |
yield f"β
Found {len(inciweb_df)} InciWeb incidents. Getting coordinates...", None, None, None, None, None, None
|
862 |
|
863 |
-
# Get coordinates for sample incidents
|
864 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
865 |
|
866 |
yield "π°οΈ Fetching NASA FIRMS fire detection data...", None, None, None, None, None, None
|
867 |
|
868 |
-
# Fetch FIRMS data
|
869 |
-
|
870 |
-
|
871 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
872 |
return
|
873 |
|
874 |
yield f"β
Found {len(firms_df)} USA fire hotspots. Matching with incidents...", None, None, None, None, None, None
|
875 |
|
876 |
-
# Match FIRMS data to InciWeb incidents
|
877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
878 |
|
879 |
yield "πΊοΈ Generating enhanced map...", None, None, None, None, None, None
|
880 |
|
881 |
-
# Generate map and visualizations
|
882 |
-
|
883 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
884 |
|
885 |
# Prepare export data - create temporary files
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
|
|
|
|
|
|
|
|
892 |
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
900 |
|
901 |
except Exception as e:
|
902 |
-
|
|
|
|
|
|
|
903 |
|
904 |
def update_plot(plot_name, state_data):
|
905 |
"""Update plot based on selection"""
|
|
|
135 |
def match_firms_to_inciweb(inciweb_df, firms_df, max_distance_km=50):
|
136 |
"""
|
137 |
Match FIRMS hotspots to InciWeb incidents based on geographic proximity
|
138 |
+
Enhanced with better error handling
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
"""
|
140 |
if firms_df.empty or inciweb_df.empty:
|
141 |
+
print("Warning: Empty dataframes passed to matching function")
|
142 |
return inciweb_df
|
143 |
|
144 |
+
try:
|
145 |
+
print(f"Matching {len(firms_df)} FIRMS hotspots to {len(inciweb_df)} InciWeb incidents...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
|
147 |
+
# Initialize new columns safely
|
148 |
+
inciweb_df = inciweb_df.copy()
|
149 |
+
inciweb_df['firms_hotspots'] = 0
|
150 |
+
inciweb_df['total_frp'] = 0.0 # Fire Radiative Power
|
151 |
+
inciweb_df['avg_confidence'] = 0.0
|
152 |
+
inciweb_df['latest_hotspot'] = None
|
153 |
+
inciweb_df['is_active'] = False
|
154 |
+
inciweb_df['hotspot_coords'] = None
|
155 |
+
inciweb_df['activity_level'] = 'Unknown'
|
156 |
|
157 |
+
# Only process incidents that have coordinates
|
158 |
+
incidents_with_coords = inciweb_df[
|
159 |
+
(inciweb_df['latitude'].notna()) & (inciweb_df['longitude'].notna())
|
160 |
+
].copy()
|
161 |
+
|
162 |
+
print(f"Processing {len(incidents_with_coords)} incidents with coordinates...")
|
163 |
+
|
164 |
+
for idx, incident in incidents_with_coords.iterrows():
|
165 |
try:
|
166 |
+
incident_coords = (incident['latitude'], incident['longitude'])
|
167 |
+
|
168 |
+
# Find FIRMS hotspots within the specified distance
|
169 |
+
hotspot_distances = []
|
170 |
+
matched_hotspots = []
|
171 |
+
|
172 |
+
for _, hotspot in firms_df.iterrows():
|
173 |
+
try:
|
174 |
+
hotspot_coords = (hotspot['latitude'], hotspot['longitude'])
|
175 |
+
distance = geodesic(incident_coords, hotspot_coords).kilometers
|
176 |
+
|
177 |
+
if distance <= max_distance_km:
|
178 |
+
hotspot_distances.append(distance)
|
179 |
+
matched_hotspots.append(hotspot)
|
180 |
+
except Exception as e:
|
181 |
+
continue # Skip invalid coordinates
|
182 |
+
|
183 |
+
if matched_hotspots:
|
184 |
+
matched_df = pd.DataFrame(matched_hotspots)
|
185 |
+
|
186 |
+
# Calculate aggregated metrics safely
|
187 |
+
num_hotspots = len(matched_hotspots)
|
188 |
+
total_frp = float(matched_df['frp'].sum()) if 'frp' in matched_df.columns else 0.0
|
189 |
+
avg_confidence = float(matched_df['confidence'].mean()) if 'confidence' in matched_df.columns else 0.0
|
190 |
+
latest_hotspot = matched_df['datetime'].max() if 'datetime' in matched_df.columns else None
|
191 |
+
|
192 |
+
# Determine activity level based on hotspot count and FRP
|
193 |
+
if num_hotspots >= 20 and total_frp >= 100:
|
194 |
+
activity_level = 'Very High'
|
195 |
+
elif num_hotspots >= 10 and total_frp >= 50:
|
196 |
+
activity_level = 'High'
|
197 |
+
elif num_hotspots >= 5 and total_frp >= 20:
|
198 |
+
activity_level = 'Medium'
|
199 |
+
elif num_hotspots >= 1:
|
200 |
+
activity_level = 'Low'
|
201 |
+
else:
|
202 |
+
activity_level = 'Minimal'
|
203 |
+
|
204 |
+
# Update the incident data
|
205 |
+
inciweb_df.at[idx, 'firms_hotspots'] = num_hotspots
|
206 |
+
inciweb_df.at[idx, 'total_frp'] = total_frp
|
207 |
+
inciweb_df.at[idx, 'avg_confidence'] = avg_confidence
|
208 |
+
inciweb_df.at[idx, 'latest_hotspot'] = latest_hotspot
|
209 |
+
inciweb_df.at[idx, 'is_active'] = True
|
210 |
+
inciweb_df.at[idx, 'activity_level'] = activity_level
|
211 |
+
|
212 |
+
# Store hotspot coordinates for visualization (simplified)
|
213 |
+
hotspot_coords = [(float(hs['latitude']), float(hs['longitude']), float(hs.get('frp', 1)))
|
214 |
+
for hs in matched_hotspots[:10]] # Limit to 10 for performance
|
215 |
+
inciweb_df.at[idx, 'hotspot_coords'] = str(hotspot_coords) # Store as string for safety
|
216 |
+
|
217 |
+
print(f" {incident['name']}: {num_hotspots} hotspots, {total_frp:.1f} FRP, {activity_level} activity")
|
218 |
+
|
219 |
except Exception as e:
|
220 |
+
print(f" Error processing incident {incident.get('name', 'Unknown')}: {e}")
|
221 |
+
continue
|
222 |
|
223 |
+
# Mark incidents without recent hotspots as potentially inactive
|
224 |
+
active_count = (inciweb_df['is_active'] == True).sum()
|
225 |
+
total_with_coords = len(incidents_with_coords)
|
226 |
+
|
227 |
+
print(f"Found {active_count} active incidents out of {total_with_coords} with coordinates")
|
228 |
+
|
229 |
+
return inciweb_df
|
230 |
+
|
231 |
+
except Exception as e:
|
232 |
+
print(f"Error in match_firms_to_inciweb: {e}")
|
233 |
+
# Return original dataframe with safety columns
|
234 |
+
inciweb_df['firms_hotspots'] = 0
|
235 |
+
inciweb_df['total_frp'] = 0.0
|
236 |
+
inciweb_df['avg_confidence'] = 0.0
|
237 |
+
inciweb_df['latest_hotspot'] = None
|
238 |
+
inciweb_df['is_active'] = False
|
239 |
+
inciweb_df['hotspot_coords'] = None
|
240 |
+
inciweb_df['activity_level'] = 'Unknown'
|
241 |
+
return inciweb_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
|
243 |
# Function to scrape InciWeb data from the accessible view page
|
244 |
def fetch_inciweb_data():
|
|
|
296 |
size_cell = row_cells[3] if len(row_cells) > 3 else None
|
297 |
if size_cell:
|
298 |
size_text = size_cell.text.strip()
|
299 |
+
|
300 |
+
# Clean up size text and handle various formats
|
301 |
+
size_text = size_text.replace('nominal', '').strip() # Remove 'nominal' text
|
302 |
+
|
303 |
+
# Extract numeric value from size text
|
304 |
+
if size_text and size_text != '':
|
305 |
+
size_match = re.search(r'(\d+(?:,\d+)*)', size_text)
|
306 |
+
if size_match:
|
307 |
+
try:
|
308 |
+
incident["size"] = int(size_match.group(1).replace(',', ''))
|
309 |
+
except ValueError:
|
310 |
+
incident["size"] = None
|
311 |
+
else:
|
312 |
+
incident["size"] = None
|
313 |
else:
|
314 |
incident["size"] = None
|
315 |
|
|
|
324 |
|
325 |
df = pd.DataFrame(incidents)
|
326 |
|
327 |
+
# Ensure all expected columns exist with safe defaults
|
328 |
+
expected_columns = {
|
329 |
+
"size": None,
|
330 |
+
"type": "Unknown",
|
331 |
+
"location": "Unknown",
|
332 |
+
"state": None,
|
333 |
+
"updated": "Unknown"
|
334 |
+
}
|
335 |
+
|
336 |
+
for col, default_val in expected_columns.items():
|
337 |
if col not in df.columns:
|
338 |
+
df[col] = default_val
|
339 |
|
340 |
+
# Safe numeric conversion with better error handling
|
341 |
+
if 'size' in df.columns:
|
342 |
+
# Clean the size column first
|
343 |
+
df['size'] = df['size'].astype(str).str.replace('nominal', '', regex=False)
|
344 |
+
df['size'] = df['size'].str.replace(r'[^\d,]', '', regex=True) # Keep only digits and commas
|
345 |
+
df['size'] = df['size'].replace('', None) # Replace empty strings with None
|
346 |
+
|
347 |
+
# Convert to numeric safely
|
348 |
+
df["size"] = pd.to_numeric(df["size"].str.replace(',', '') if df["size"].dtype == 'object' else df["size"], errors="coerce")
|
349 |
|
350 |
print(f"Fetched {len(df)} incidents")
|
351 |
return df
|
|
|
537 |
|
538 |
# Enhanced map generation with FIRMS data
|
539 |
def generate_enhanced_map(df, firms_df):
|
540 |
+
"""Generate map with both InciWeb incidents and FIRMS hotspots - with robust error handling"""
|
541 |
+
|
542 |
+
try:
|
543 |
+
print("Starting map generation...")
|
544 |
+
|
545 |
+
# Create map centered on the US
|
546 |
+
m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
|
547 |
+
|
548 |
+
# Add FIRMS heat map layer for all USA hotspots (even if no InciWeb coordinates)
|
549 |
+
if not firms_df.empty:
|
550 |
+
print(f"Adding {len(firms_df)} FIRMS hotspots to map...")
|
551 |
+
try:
|
552 |
+
# Limit to first 1000 hotspots for performance
|
553 |
+
sample_firms = firms_df.head(1000)
|
554 |
+
heat_data = []
|
555 |
+
|
556 |
+
for _, row in sample_firms.iterrows():
|
557 |
+
try:
|
558 |
+
lat, lon = float(row['latitude']), float(row['longitude'])
|
559 |
+
frp = float(row.get('frp', 1))
|
560 |
+
if -90 <= lat <= 90 and -180 <= lon <= 180: # Valid coordinates
|
561 |
+
heat_data.append([lat, lon, min(frp, 100)])
|
562 |
+
except (ValueError, TypeError):
|
563 |
+
continue
|
564 |
+
|
565 |
+
if heat_data:
|
566 |
+
HeatMap(
|
567 |
+
heat_data,
|
568 |
+
name="Fire Intensity Heatmap (NASA FIRMS)",
|
569 |
+
radius=15,
|
570 |
+
blur=10,
|
571 |
+
max_zoom=1,
|
572 |
+
gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}
|
573 |
+
).add_to(m)
|
574 |
+
print(f"Added heatmap with {len(heat_data)} valid hotspots")
|
575 |
+
|
576 |
+
# Add some sample FIRMS points as individual markers
|
577 |
+
for i, (_, hotspot) in enumerate(sample_firms.head(50).iterrows()):
|
578 |
+
try:
|
579 |
+
lat, lon = float(hotspot['latitude']), float(hotspot['longitude'])
|
580 |
+
frp = float(hotspot.get('frp', 1))
|
581 |
+
conf = hotspot.get('confidence', 'N/A')
|
582 |
+
acq_time = hotspot.get('acq_time', 'N/A')
|
583 |
+
|
584 |
+
if -90 <= lat <= 90 and -180 <= lon <= 180:
|
585 |
+
folium.CircleMarker(
|
586 |
+
location=[lat, lon],
|
587 |
+
radius=2 + min(frp / 10, 8),
|
588 |
+
popup=f"π₯ FIRMS Hotspot<br>FRP: {frp} MW<br>Confidence: {conf}%<br>Time: {acq_time}",
|
589 |
+
color='red',
|
590 |
+
fillColor='orange',
|
591 |
+
fillOpacity=0.7,
|
592 |
+
weight=1
|
593 |
+
).add_to(m)
|
594 |
+
except (ValueError, TypeError, KeyError):
|
595 |
+
continue
|
596 |
+
|
597 |
+
except Exception as e:
|
598 |
+
print(f"Error adding FIRMS data to map: {e}")
|
599 |
+
|
600 |
+
# Add incident markers if we have coordinates
|
601 |
+
incidents_with_coords = df[(df['latitude'].notna()) & (df['longitude'].notna())] if not df.empty else pd.DataFrame()
|
602 |
+
|
603 |
active_incidents = 0
|
604 |
inactive_incidents = 0
|
605 |
|
606 |
+
if not incidents_with_coords.empty:
|
607 |
+
print(f"Adding {len(incidents_with_coords)} InciWeb incidents with coordinates to map...")
|
608 |
|
609 |
+
# Add incident markers with error handling
|
610 |
+
try:
|
611 |
+
incident_cluster = MarkerCluster(name="InciWeb Incidents").add_to(m)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
612 |
|
613 |
+
for _, row in incidents_with_coords.iterrows():
|
614 |
+
try:
|
615 |
+
lat, lon = float(row['latitude']), float(row['longitude'])
|
616 |
+
|
617 |
+
if not (-90 <= lat <= 90 and -180 <= lon <= 180):
|
618 |
+
continue
|
619 |
+
|
620 |
+
# Determine marker color based on activity and type
|
621 |
+
is_active = row.get('is_active', False)
|
622 |
+
if is_active:
|
623 |
+
active_incidents += 1
|
624 |
+
activity_level = row.get('activity_level', 'Unknown')
|
625 |
+
if activity_level == 'Very High':
|
626 |
+
color = 'red'
|
627 |
+
elif activity_level == 'High':
|
628 |
+
color = 'orange'
|
629 |
+
elif activity_level == 'Medium':
|
630 |
+
color = 'yellow'
|
631 |
+
else:
|
632 |
+
color = 'lightred'
|
633 |
+
else:
|
634 |
+
inactive_incidents += 1
|
635 |
+
color = 'gray'
|
636 |
+
|
637 |
+
# Create popup content safely
|
638 |
+
name = str(row.get('name', 'Unknown'))
|
639 |
+
incident_type = str(row.get('type', 'N/A'))
|
640 |
+
location = str(row.get('location', 'N/A'))
|
641 |
+
size = row.get('size', 'N/A')
|
642 |
+
updated = str(row.get('updated', 'N/A'))
|
643 |
+
|
644 |
+
firms_hotspots = int(row.get('firms_hotspots', 0))
|
645 |
+
total_frp = float(row.get('total_frp', 0))
|
646 |
+
avg_confidence = float(row.get('avg_confidence', 0))
|
647 |
+
activity_level = str(row.get('activity_level', 'Unknown'))
|
648 |
+
|
649 |
+
popup_content = f"""
|
650 |
+
<div style="width: 300px;">
|
651 |
+
<h4>{name}</h4>
|
652 |
+
<b>Type:</b> {incident_type}<br>
|
653 |
+
<b>Location:</b> {location}<br>
|
654 |
+
<b>Size:</b> {size} acres<br>
|
655 |
+
<b>Last Updated:</b> {updated}<br>
|
656 |
+
|
657 |
+
<hr style="margin: 10px 0;">
|
658 |
+
<h5>π₯ Fire Activity (NASA FIRMS)</h5>
|
659 |
+
<b>Status:</b> {'π΄ ACTIVE' if is_active else 'β« Inactive'}<br>
|
660 |
+
<b>Activity Level:</b> {activity_level}<br>
|
661 |
+
<b>Hotspots (24h):</b> {firms_hotspots}<br>
|
662 |
+
<b>Total Fire Power:</b> {total_frp:.1f} MW<br>
|
663 |
+
<b>Avg Confidence:</b> {avg_confidence:.1f}%<br>
|
664 |
+
</div>
|
665 |
+
"""
|
666 |
+
|
667 |
+
folium.Marker(
|
668 |
+
location=[lat, lon],
|
669 |
+
popup=folium.Popup(popup_content, max_width=350),
|
670 |
+
icon=folium.Icon(color=color)
|
671 |
+
).add_to(incident_cluster)
|
672 |
+
|
673 |
+
except Exception as e:
|
674 |
+
print(f"Error adding incident marker: {e}")
|
675 |
+
continue
|
676 |
+
|
677 |
+
except Exception as e:
|
678 |
+
print(f"Error creating incident markers: {e}")
|
679 |
+
else:
|
680 |
+
print("No InciWeb incidents have coordinates, showing FIRMS data only")
|
681 |
+
inactive_incidents = len(df) if not df.empty else 0
|
682 |
+
|
683 |
+
# Add custom legend
|
684 |
+
total_hotspots = len(firms_df) if not firms_df.empty else 0
|
685 |
+
total_incidents = len(df) if not df.empty else 0
|
686 |
+
|
687 |
+
legend_html = f'''
|
688 |
+
<div style="position: fixed;
|
689 |
+
bottom: 50px; left: 50px; width: 250px; height: 320px;
|
690 |
+
border:2px solid grey; z-index:9999; font-size:12px;
|
691 |
+
background-color:white; padding: 10px;
|
692 |
+
border-radius: 5px; font-family: Arial;">
|
693 |
+
<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">π₯ Wildfire Activity Status</div>
|
694 |
+
|
695 |
+
<div style="margin-bottom: 8px;"><b>InciWeb Incidents:</b></div>
|
696 |
+
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
697 |
+
<div style="background-color: red; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
698 |
+
<div>Very High Activity</div>
|
699 |
+
</div>
|
700 |
+
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
701 |
+
<div style="background-color: orange; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
702 |
+
<div>High Activity</div>
|
703 |
+
</div>
|
704 |
+
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
705 |
+
<div style="background-color: yellow; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
706 |
+
<div>Medium Activity</div>
|
707 |
+
</div>
|
708 |
+
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
709 |
+
<div style="background-color: lightcoral; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
710 |
+
<div>Low Activity</div>
|
711 |
+
</div>
|
712 |
+
<div style="display: flex; align-items: center; margin-bottom: 8px;">
|
713 |
+
<div style="background-color: gray; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
714 |
+
<div>Inactive/No Data</div>
|
715 |
</div>
|
|
|
716 |
|
717 |
+
<div style="margin-bottom: 5px;"><b>NASA FIRMS Data:</b></div>
|
718 |
+
<div style="display: flex; align-items: center; margin-bottom: 3px;">
|
719 |
+
<div style="background-color: orange; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
|
720 |
+
<div>Fire Hotspots (24h)</div>
|
721 |
+
</div>
|
722 |
+
<div style="margin-bottom: 8px; font-style: italic;">Heat map shows fire intensity</div>
|
723 |
|
724 |
+
<div style="font-size: 11px; margin-top: 10px; padding-top: 5px; border-top: 1px solid #ccc;">
|
725 |
+
<b>Statistics:</b><br>
|
726 |
+
π΄ Active InciWeb: {active_incidents}<br>
|
727 |
+
β« Inactive InciWeb: {inactive_incidents}<br>
|
728 |
+
π Total InciWeb: {total_incidents}<br>
|
729 |
+
π‘οΈ Total FIRMS Hotspots: {total_hotspots}<br>
|
730 |
+
π Incidents with Coords: {len(incidents_with_coords)}
|
731 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
732 |
</div>
|
733 |
+
'''
|
734 |
|
735 |
+
# Add layer control
|
736 |
+
try:
|
737 |
+
folium.LayerControl().add_to(m)
|
738 |
+
except Exception as e:
|
739 |
+
print(f"Error adding layer control: {e}")
|
740 |
+
|
741 |
+
# Get map HTML and add legend
|
742 |
+
try:
|
743 |
+
map_html = m._repr_html_()
|
744 |
+
map_with_legend = map_html.replace('</body>', legend_html + '</body>')
|
745 |
+
print("Map generation completed successfully")
|
746 |
+
return map_with_legend
|
747 |
+
except Exception as e:
|
748 |
+
print(f"Error generating final map HTML: {e}")
|
749 |
+
return f"<div style='padding: 20px; text-align: center;'>Map generation error: {str(e)}</div>"
|
750 |
+
|
751 |
+
except Exception as e:
|
752 |
+
print(f"Critical error in map generation: {e}")
|
753 |
+
import traceback
|
754 |
+
traceback.print_exc()
|
755 |
+
return f"<div style='padding: 20px; text-align: center;'>Critical map error: {str(e)}</div>"
|
|
|
|
|
|
|
|
|
|
|
756 |
|
757 |
# Enhanced visualization functions
|
758 |
def generate_enhanced_visualizations(df, firms_df):
|
759 |
+
"""Generate enhanced visualizations with FIRMS data integration - with robust error handling"""
|
760 |
figures = []
|
761 |
|
762 |
+
try:
|
763 |
+
print("Starting visualization generation...")
|
764 |
+
|
765 |
+
if df.empty:
|
766 |
+
print("Warning: Empty dataframe for visualizations")
|
767 |
+
return [px.bar(title="No data available")]
|
768 |
+
|
769 |
+
# 1. Activity Status Overview
|
770 |
+
try:
|
771 |
+
if 'is_active' in df.columns:
|
772 |
+
activity_counts = df['is_active'].value_counts()
|
773 |
+
if not activity_counts.empty:
|
774 |
+
activity_summary = activity_counts.reset_index()
|
775 |
+
activity_summary.columns = ['is_active', 'count']
|
776 |
+
activity_summary['status'] = activity_summary['is_active'].map({
|
777 |
+
True: 'Active (FIRMS detected)',
|
778 |
+
False: 'Inactive/Unknown'
|
779 |
+
})
|
780 |
+
|
781 |
+
fig1 = px.pie(
|
782 |
+
activity_summary,
|
783 |
+
values='count',
|
784 |
+
names='status',
|
785 |
+
title="π₯ Wildfire Activity Status (Based on NASA FIRMS Data)",
|
786 |
+
color_discrete_map={
|
787 |
+
'Active (FIRMS detected)': 'red',
|
788 |
+
'Inactive/Unknown': 'gray'
|
789 |
+
}
|
790 |
+
)
|
791 |
+
fig1.update_traces(textinfo='label+percent+value')
|
792 |
+
else:
|
793 |
+
fig1 = px.bar(title="No activity status data available")
|
794 |
+
else:
|
795 |
+
fig1 = px.bar(title="Activity status data not available")
|
796 |
+
except Exception as e:
|
797 |
+
print(f"Error creating activity status chart: {e}")
|
798 |
+
fig1 = px.bar(title=f"Activity status error: {str(e)}")
|
799 |
+
figures.append(fig1)
|
800 |
+
|
801 |
+
# 2. Incident Type Distribution (Simple fallback)
|
802 |
+
try:
|
803 |
+
if 'type' in df.columns and df['type'].notna().any():
|
804 |
+
type_counts = df['type'].value_counts().head(10).reset_index()
|
805 |
+
type_counts.columns = ['incident_type', 'count']
|
806 |
+
|
807 |
+
fig2 = px.bar(
|
808 |
+
type_counts,
|
809 |
+
x='incident_type',
|
810 |
+
y='count',
|
811 |
+
title="π Incidents by Type",
|
812 |
+
labels={'incident_type': 'Incident Type', 'count': 'Count'}
|
813 |
+
)
|
814 |
+
else:
|
815 |
+
fig2 = px.bar(title="No incident type data available")
|
816 |
+
except Exception as e:
|
817 |
+
print(f"Error creating type distribution chart: {e}")
|
818 |
+
fig2 = px.bar(title=f"Type distribution error: {str(e)}")
|
819 |
+
figures.append(fig2)
|
820 |
+
|
821 |
+
# 3. State Distribution
|
822 |
+
try:
|
823 |
+
if 'state' in df.columns and df['state'].notna().any():
|
824 |
+
state_counts = df['state'].value_counts().head(10).reset_index()
|
825 |
+
state_counts.columns = ['state_name', 'count']
|
826 |
+
|
827 |
+
fig3 = px.bar(
|
828 |
+
state_counts,
|
829 |
+
x='state_name',
|
830 |
+
y='count',
|
831 |
+
title="πΊοΈ Incidents by State (Top 10)",
|
832 |
+
labels={'state_name': 'State', 'count': 'Count'}
|
833 |
+
)
|
834 |
+
else:
|
835 |
+
fig3 = px.bar(title="No state data available")
|
836 |
+
except Exception as e:
|
837 |
+
print(f"Error creating state distribution chart: {e}")
|
838 |
+
fig3 = px.bar(title=f"State distribution error: {str(e)}")
|
839 |
+
figures.append(fig3)
|
840 |
+
|
841 |
+
# 4. FIRMS Hotspot Timeline (if available)
|
842 |
+
try:
|
843 |
+
if not firms_df.empty and 'datetime' in firms_df.columns:
|
844 |
+
# Group by hour to show detection pattern
|
845 |
+
firms_df_copy = firms_df.copy()
|
846 |
+
firms_df_copy['hour'] = pd.to_datetime(firms_df_copy['datetime']).dt.floor('H')
|
847 |
+
hourly_detections = firms_df_copy.groupby('hour').size().reset_index(name='detections')
|
848 |
+
|
849 |
+
if not hourly_detections.empty:
|
850 |
+
fig4 = px.line(
|
851 |
+
hourly_detections,
|
852 |
+
x='hour',
|
853 |
+
y='detections',
|
854 |
+
title="π Fire Hotspot Detections Over Time (Last 24 Hours)",
|
855 |
+
labels={'hour': 'Time', 'detections': 'Number of Hotspots Detected'}
|
856 |
+
)
|
857 |
+
fig4.update_traces(line_color='red')
|
858 |
+
else:
|
859 |
+
fig4 = px.bar(title="No FIRMS temporal data available")
|
860 |
+
else:
|
861 |
+
fig4 = px.bar(title="FIRMS temporal data not available")
|
862 |
+
except Exception as e:
|
863 |
+
print(f"Error creating FIRMS timeline chart: {e}")
|
864 |
+
fig4 = px.bar(title=f"FIRMS timeline error: {str(e)}")
|
865 |
+
figures.append(fig4)
|
866 |
+
|
867 |
+
# 5. Fire Activity Level Distribution (if available)
|
868 |
+
try:
|
869 |
+
if 'activity_level' in df.columns and df['activity_level'].notna().any():
|
870 |
+
# Filter out Unknown values
|
871 |
+
activity_df = df[df['activity_level'] != 'Unknown']
|
872 |
+
if not activity_df.empty and activity_df['activity_level'].notna().any():
|
873 |
+
activity_levels = activity_df['activity_level'].value_counts().reset_index()
|
874 |
+
activity_levels.columns = ['activity_level', 'count']
|
875 |
+
|
876 |
+
# Define order and colors
|
877 |
+
level_order = ['Very High', 'High', 'Medium', 'Low', 'Minimal']
|
878 |
+
color_map = {
|
879 |
+
'Very High': 'darkred',
|
880 |
+
'High': 'red',
|
881 |
+
'Medium': 'orange',
|
882 |
+
'Low': 'yellow',
|
883 |
+
'Minimal': 'lightblue'
|
884 |
+
}
|
885 |
+
|
886 |
+
fig5 = px.bar(
|
887 |
+
activity_levels,
|
888 |
+
x='activity_level',
|
889 |
+
y='count',
|
890 |
+
title="π Fire Activity Levels (NASA FIRMS Intensity)",
|
891 |
+
labels={'activity_level': 'Activity Level', 'count': 'Number of Incidents'},
|
892 |
+
color='activity_level',
|
893 |
+
color_discrete_map=color_map,
|
894 |
+
category_orders={'activity_level': level_order}
|
895 |
+
)
|
896 |
+
else:
|
897 |
+
fig5 = px.bar(title="No activity level data with known values")
|
898 |
+
else:
|
899 |
+
fig5 = px.bar(title="Activity level data not available")
|
900 |
+
except Exception as e:
|
901 |
+
print(f"Error creating activity level chart: {e}")
|
902 |
+
fig5 = px.bar(title=f"Activity level error: {str(e)}")
|
903 |
+
figures.append(fig5)
|
904 |
+
|
905 |
+
print(f"Generated {len(figures)} visualizations")
|
906 |
+
return figures
|
907 |
+
|
908 |
+
except Exception as e:
|
909 |
+
print(f"Critical error in visualization generation: {e}")
|
910 |
+
import traceback
|
911 |
+
traceback.print_exc()
|
912 |
+
return [px.bar(title=f"Critical visualization error: {str(e)}")]
|
913 |
|
914 |
# Main application function
|
915 |
def create_enhanced_wildfire_app():
|
|
|
967 |
app_state = gr.State({})
|
968 |
|
969 |
def fetch_and_process_data():
|
970 |
+
"""Main data processing function with comprehensive error handling and debugging"""
|
971 |
try:
|
972 |
yield "π‘ Fetching InciWeb incident data...", None, None, None, None, None, None
|
973 |
|
974 |
+
# Fetch InciWeb data with error handling
|
975 |
+
try:
|
976 |
+
print("Step 1: Fetching InciWeb data...")
|
977 |
+
inciweb_df = fetch_inciweb_data()
|
978 |
+
if inciweb_df.empty:
|
979 |
+
yield "β Failed to fetch InciWeb data", None, None, None, None, None, None
|
980 |
+
return
|
981 |
+
print(f"Step 1 SUCCESS: Got {len(inciweb_df)} incidents")
|
982 |
+
except Exception as e:
|
983 |
+
print(f"Step 1 ERROR: {e}")
|
984 |
+
yield f"β Error fetching InciWeb data: {str(e)}", None, None, None, None, None, None
|
985 |
return
|
986 |
|
987 |
yield f"β
Found {len(inciweb_df)} InciWeb incidents. Getting coordinates...", None, None, None, None, None, None
|
988 |
|
989 |
+
# Get coordinates for sample incidents with error handling
|
990 |
+
try:
|
991 |
+
print("Step 2: Getting coordinates...")
|
992 |
+
inciweb_df = add_coordinates_to_incidents(inciweb_df, max_incidents=15)
|
993 |
+
coords_count = len(inciweb_df[(inciweb_df['latitude'].notna()) & (inciweb_df['longitude'].notna())])
|
994 |
+
print(f"Step 2 SUCCESS: Got coordinates for {coords_count} incidents")
|
995 |
+
except Exception as e:
|
996 |
+
print(f"Step 2 ERROR: {e}")
|
997 |
+
# Continue with the data we have
|
998 |
|
999 |
yield "π°οΈ Fetching NASA FIRMS fire detection data...", None, None, None, None, None, None
|
1000 |
|
1001 |
+
# Fetch FIRMS data with error handling
|
1002 |
+
try:
|
1003 |
+
print("Step 3: Fetching FIRMS data...")
|
1004 |
+
firms_df = fetch_firms_data()
|
1005 |
+
if firms_df.empty:
|
1006 |
+
print("Step 3 WARNING: FIRMS data empty")
|
1007 |
+
# Still useful to show InciWeb data even without FIRMS
|
1008 |
+
yield "β οΈ FIRMS data unavailable, generating basic visualization...", None, None, None, None, None, None
|
1009 |
+
|
1010 |
+
# Generate basic map and visualizations without FIRMS data
|
1011 |
+
try:
|
1012 |
+
print("Generating basic map without FIRMS...")
|
1013 |
+
map_html = generate_enhanced_map(inciweb_df, pd.DataFrame())
|
1014 |
+
print("Generating basic visualizations...")
|
1015 |
+
plots = generate_enhanced_visualizations(inciweb_df, pd.DataFrame())
|
1016 |
+
|
1017 |
+
# Create CSV file
|
1018 |
+
import tempfile
|
1019 |
+
csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
|
1020 |
+
inciweb_df.to_csv(csv_file.name, index=False)
|
1021 |
+
csv_file.close()
|
1022 |
+
|
1023 |
+
final_status = f"β
Partial success! Found {len(inciweb_df)} InciWeb incidents (FIRMS data unavailable)"
|
1024 |
+
yield (final_status, map_html, plots[0], inciweb_df, pd.DataFrame(), csv_file.name,
|
1025 |
+
{"inciweb_df": inciweb_df, "firms_df": pd.DataFrame(), "plots": plots})
|
1026 |
+
return
|
1027 |
+
except Exception as e:
|
1028 |
+
print(f"Error in basic visualization: {e}")
|
1029 |
+
yield f"β Error in basic visualization: {str(e)}", None, None, inciweb_df, pd.DataFrame(), None, None
|
1030 |
+
return
|
1031 |
+
|
1032 |
+
print(f"Step 3 SUCCESS: Got {len(firms_df)} FIRMS hotspots")
|
1033 |
+
|
1034 |
+
except Exception as e:
|
1035 |
+
print(f"Step 3 ERROR: {e}")
|
1036 |
+
yield f"β Error fetching FIRMS data: {str(e)}", None, None, inciweb_df, pd.DataFrame(), None, None
|
1037 |
return
|
1038 |
|
1039 |
yield f"β
Found {len(firms_df)} USA fire hotspots. Matching with incidents...", None, None, None, None, None, None
|
1040 |
|
1041 |
+
# Match FIRMS data to InciWeb incidents with error handling
|
1042 |
+
try:
|
1043 |
+
print("Step 4: Matching FIRMS to InciWeb...")
|
1044 |
+
enhanced_df = match_firms_to_inciweb(inciweb_df, firms_df)
|
1045 |
+
print(f"Step 4 SUCCESS: Enhanced {len(enhanced_df)} incidents")
|
1046 |
+
except Exception as e:
|
1047 |
+
print(f"Step 4 ERROR: {e}")
|
1048 |
+
# Use original data if matching fails
|
1049 |
+
enhanced_df = inciweb_df
|
1050 |
+
print("Using original InciWeb data without FIRMS matching")
|
1051 |
|
1052 |
yield "πΊοΈ Generating enhanced map...", None, None, None, None, None, None
|
1053 |
|
1054 |
+
# Generate map and visualizations with error handling
|
1055 |
+
try:
|
1056 |
+
print("Step 5: Generating map...")
|
1057 |
+
map_html = generate_enhanced_map(enhanced_df, firms_df)
|
1058 |
+
print("Step 5a SUCCESS: Map generated")
|
1059 |
+
|
1060 |
+
print("Step 5: Generating visualizations...")
|
1061 |
+
plots = generate_enhanced_visualizations(enhanced_df, firms_df)
|
1062 |
+
print("Step 5b SUCCESS: Visualizations generated")
|
1063 |
+
except Exception as e:
|
1064 |
+
print(f"Step 5 ERROR: {e}")
|
1065 |
+
# Create fallback simple content
|
1066 |
+
map_html = f"<div style='padding: 20px; text-align: center;'>Map generation failed: {str(e)}<br>Data is available in tables below.</div>"
|
1067 |
+
plots = [px.bar(title=f"Visualization generation failed: {str(e)}")]
|
1068 |
|
1069 |
# Prepare export data - create temporary files
|
1070 |
+
try:
|
1071 |
+
print("Step 6: Creating CSV export...")
|
1072 |
+
import tempfile
|
1073 |
+
csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
|
1074 |
+
enhanced_df.to_csv(csv_file.name, index=False)
|
1075 |
+
csv_file.close()
|
1076 |
+
print("Step 6 SUCCESS: CSV created")
|
1077 |
+
except Exception as e:
|
1078 |
+
print(f"Step 6 ERROR: {e}")
|
1079 |
+
csv_file = None
|
1080 |
|
1081 |
+
# Calculate final statistics
|
1082 |
+
try:
|
1083 |
+
active_count = (enhanced_df.get('is_active', pd.Series([False])) == True).sum()
|
1084 |
+
total_hotspots = len(firms_df) if not firms_df.empty else 0
|
1085 |
+
coords_count = len(enhanced_df[(enhanced_df['latitude'].notna()) & (enhanced_df['longitude'].notna())])
|
1086 |
+
|
1087 |
+
final_status = f"β
Complete! {active_count} active fires, {total_hotspots} hotspots, {coords_count} with coordinates"
|
1088 |
+
print(f"FINAL SUCCESS: {final_status}")
|
1089 |
+
|
1090 |
+
yield (final_status, map_html, plots[0], enhanced_df, firms_df, csv_file.name if csv_file else None,
|
1091 |
+
{"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
|
1092 |
+
except Exception as e:
|
1093 |
+
print(f"Error calculating final statistics: {e}")
|
1094 |
+
final_status = "β
Process completed with some errors"
|
1095 |
+
yield (final_status, map_html, plots[0], enhanced_df, firms_df, csv_file.name if csv_file else None,
|
1096 |
+
{"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
|
1097 |
|
1098 |
except Exception as e:
|
1099 |
+
import traceback
|
1100 |
+
error_details = traceback.format_exc()
|
1101 |
+
print(f"CRITICAL ERROR in main process: {error_details}")
|
1102 |
+
yield f"β Critical Error: {str(e)}", None, None, None, None, None, None
|
1103 |
|
1104 |
def update_plot(plot_name, state_data):
|
1105 |
"""Update plot based on selection"""
|