nakas commited on
Commit
6da1d69
Β·
verified Β·
1 Parent(s): a2e11fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +598 -398
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
- print(f"Matching {len(firms_df)} FIRMS hotspots to {len(inciweb_df)} InciWeb incidents...")
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
- # Find FIRMS hotspots within the specified distance
173
- hotspot_distances = []
174
- matched_hotspots = []
 
 
 
 
 
 
175
 
176
- for _, hotspot in firms_df.iterrows():
177
- hotspot_coords = (hotspot['latitude'], hotspot['longitude'])
178
-
 
 
 
 
 
179
  try:
180
- distance = geodesic(incident_coords, hotspot_coords).kilometers
181
- if distance <= max_distance_km:
182
- hotspot_distances.append(distance)
183
- matched_hotspots.append(hotspot)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  except Exception as e:
185
- continue # Skip invalid coordinates
 
186
 
187
- if matched_hotspots:
188
- matched_df = pd.DataFrame(matched_hotspots)
189
-
190
- # Calculate aggregated metrics
191
- num_hotspots = len(matched_hotspots)
192
- total_frp = matched_df['frp'].sum() if 'frp' in matched_df.columns else 0
193
- avg_confidence = matched_df['confidence'].mean() if 'confidence' in matched_df.columns else 0
194
- latest_hotspot = matched_df['datetime'].max() if 'datetime' in matched_df.columns else None
195
-
196
- # Determine activity level based on hotspot count and FRP
197
- if num_hotspots >= 20 and total_frp >= 100:
198
- activity_level = 'Very High'
199
- elif num_hotspots >= 10 and total_frp >= 50:
200
- activity_level = 'High'
201
- elif num_hotspots >= 5 and total_frp >= 20:
202
- activity_level = 'Medium'
203
- elif num_hotspots >= 1:
204
- activity_level = 'Low'
205
- else:
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
- size_match = re.search(r'(\d+(?:,\d+)*)', size_text)
288
- if size_match:
289
- incident["size"] = int(size_match.group(1).replace(',', ''))
 
 
 
 
 
 
 
 
 
 
 
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
- for col in ["size", "type", "location", "state", "updated"]:
 
 
 
 
 
 
 
 
306
  if col not in df.columns:
307
- df[col] = None
308
 
309
- df["size"] = pd.to_numeric(df["size"], errors="coerce")
 
 
 
 
 
 
 
 
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
- # Create map centered on the US
503
- m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
504
-
505
- # Add FIRMS heat map layer for all USA hotspots (even if no InciWeb coordinates)
506
- if not firms_df.empty:
507
- print(f"Adding {len(firms_df)} FIRMS hotspots to map...")
508
- heat_data = [[row['latitude'], row['longitude'], min(row.get('frp', 1), 100)]
509
- for _, row in firms_df.iterrows()]
510
-
511
- if heat_data:
512
- HeatMap(
513
- heat_data,
514
- name="Fire Intensity Heatmap (NASA FIRMS)",
515
- radius=15,
516
- blur=10,
517
- max_zoom=1,
518
- gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}
519
- ).add_to(m)
520
-
521
- # Add some sample FIRMS points as markers
522
- sample_firms = firms_df.head(100) # Show top 100 hotspots as individual markers
523
- for _, hotspot in sample_firms.iterrows():
524
- folium.CircleMarker(
525
- location=[hotspot['latitude'], hotspot['longitude']],
526
- radius=2 + min(hotspot.get('frp', 1) / 10, 8),
527
- popup=f"πŸ”₯ FIRMS Hotspot<br>FRP: {hotspot.get('frp', 'N/A')} MW<br>Confidence: {hotspot.get('confidence', 'N/A')}%<br>Time: {hotspot.get('acq_time', 'N/A')}",
528
- color='red',
529
- fillColor='orange',
530
- fillOpacity=0.7,
531
- weight=1
532
- ).add_to(m)
533
-
534
- # Add incident markers if we have coordinates
535
- incidents_with_coords = df[(df['latitude'].notna()) & (df['longitude'].notna())]
536
-
537
- if not incidents_with_coords.empty:
538
- print(f"Adding {len(incidents_with_coords)} InciWeb incidents with coordinates to map...")
539
-
540
- # Add incident markers
541
- incident_cluster = MarkerCluster(name="InciWeb Incidents").add_to(m)
542
-
543
- # Track statistics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  active_incidents = 0
545
  inactive_incidents = 0
546
 
547
- for _, row in incidents_with_coords.iterrows():
548
- lat, lon = row['latitude'], row['longitude']
549
 
550
- # Determine marker color based on activity and type
551
- if row.get('is_active', False):
552
- active_incidents += 1
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
- <a href="{row.get('link', '#')}" target="_blank">πŸ“‹ More Details</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  </div>
590
- """
591
 
592
- folium.Marker(
593
- location=[lat, lon],
594
- popup=folium.Popup(popup_content, max_width=350),
595
- icon=folium.Icon(color=color, icon=icon, prefix='fa')
596
- ).add_to(incident_cluster)
 
597
 
598
- # Add hotspot visualization for active incidents
599
- if row.get('is_active', False) and row.get('hotspot_coords'):
600
- hotspot_coords = row.get('hotspot_coords', [])
601
- if hotspot_coords:
602
- # Add individual hotspot markers (smaller, less intrusive)
603
- for coord in hotspot_coords[:20]: # Limit to 20 hotspots per incident
604
- folium.CircleMarker(
605
- location=[coord[0], coord[1]],
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
- <div style="margin-bottom: 5px;"><b>NASA FIRMS Data:</b></div>
652
- <div style="display: flex; align-items: center; margin-bottom: 3px;">
653
- <div style="background-color: orange; width: 12px; height: 12px; margin-right: 5px; border-radius: 50%;"></div>
654
- <div>Fire Hotspots (24h)</div>
655
- </div>
656
- <div style="margin-bottom: 8px; font-style: italic;">Heat map shows fire intensity</div>
657
-
658
- <div style="font-size: 11px; margin-top: 10px; padding-top: 5px; border-top: 1px solid #ccc;">
659
- <b>Statistics:</b><br>
660
- πŸ”΄ Active InciWeb: {active_incidents}<br>
661
- ⚫ Inactive InciWeb: {inactive_incidents}<br>
662
- πŸ“ Total InciWeb: {total_incidents}<br>
663
- 🌑️ Total FIRMS Hotspots: {total_hotspots}<br>
664
- πŸ“Š Incidents with Coords: {len(incidents_with_coords)}
665
- </div>
666
- </div>
667
- '''
668
-
669
- # Add layer control
670
- folium.LayerControl().add_to(m)
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
- if df.empty:
684
- return [px.bar(title="No data available")]
685
-
686
- # 1. Activity Status Overview
687
- if 'is_active' in df.columns:
688
- activity_summary = df['is_active'].value_counts().reset_index()
689
- activity_summary.columns = ['is_active', 'count']
690
- activity_summary['status'] = activity_summary['is_active'].map({True: 'Active (FIRMS detected)', False: 'Inactive/Unknown'})
691
-
692
- fig1 = px.pie(
693
- activity_summary,
694
- values='count',
695
- names='status',
696
- title="πŸ”₯ Wildfire Activity Status (Based on NASA FIRMS Data)",
697
- color_discrete_map={'Active (FIRMS detected)': 'red', 'Inactive/Unknown': 'gray'}
698
- )
699
- fig1.update_traces(textinfo='label+percent+value')
700
- else:
701
- fig1 = px.bar(title="Activity status data not available")
702
- figures.append(fig1)
703
-
704
- # 2. Activity Level Distribution
705
- if 'activity_level' in df.columns and df['activity_level'].notna().any():
706
- activity_levels = df[df['activity_level'] != 'Unknown']['activity_level'].value_counts().reset_index()
707
- activity_levels.columns = ['activity_level', 'count']
708
-
709
- # Define order and colors
710
- level_order = ['Very High', 'High', 'Medium', 'Low', 'Minimal']
711
- color_map = {'Very High': 'darkred', 'High': 'red', 'Medium': 'orange', 'Low': 'yellow', 'Minimal': 'lightblue'}
712
-
713
- fig2 = px.bar(
714
- activity_levels,
715
- x='activity_level',
716
- y='count',
717
- title="πŸ“Š Fire Activity Levels (NASA FIRMS Intensity)",
718
- labels={'activity_level': 'Activity Level', 'count': 'Number of Incidents'},
719
- color='activity_level',
720
- color_discrete_map=color_map,
721
- category_orders={'activity_level': level_order}
722
- )
723
- else:
724
- fig2 = px.bar(title="Activity level data not available")
725
- figures.append(fig2)
726
-
727
- # 3. Fire Radiative Power vs Incident Size
728
- if 'total_frp' in df.columns and 'size' in df.columns:
729
- active_df = df[(df['is_active'] == True) & (df['total_frp'] > 0) & (df['size'].notna())].copy()
730
-
731
- if not active_df.empty:
732
- fig3 = px.scatter(
733
- active_df,
734
- x='size',
735
- y='total_frp',
736
- size='firms_hotspots',
737
- color='activity_level',
738
- hover_data=['name', 'state', 'firms_hotspots'],
739
- title="πŸ”₯ Fire Intensity vs Incident Size (Active Fires Only)",
740
- labels={'size': 'Incident Size (acres)', 'total_frp': 'Total Fire Radiative Power (MW)'},
741
- color_discrete_map={'Very High': 'darkred', 'High': 'red', 'Medium': 'orange', 'Low': 'yellow'}
742
- )
743
- fig3.update_layout(xaxis_type="log", yaxis_type="log")
744
- else:
745
- fig3 = px.bar(title="No active fires with size and intensity data")
746
- else:
747
- fig3 = px.bar(title="Fire intensity vs size data not available")
748
- figures.append(fig3)
749
-
750
- # 4. Hotspot Detection Over Time (if FIRMS data available)
751
- if not firms_df.empty and 'datetime' in firms_df.columns:
752
- # Group by hour to show detection pattern
753
- firms_df['hour'] = firms_df['datetime'].dt.floor('H')
754
- hourly_detections = firms_df.groupby('hour').size().reset_index(name='detections')
755
-
756
- fig4 = px.line(
757
- hourly_detections,
758
- x='hour',
759
- y='detections',
760
- title="πŸ• Fire Hotspot Detections Over Time (Last 24 Hours)",
761
- labels={'hour': 'Time', 'detections': 'Number of Hotspots Detected'}
762
- )
763
- fig4.update_traces(line_color='red')
764
- else:
765
- fig4 = px.bar(title="FIRMS temporal data not available")
766
- figures.append(fig4)
767
-
768
- # 5. State-wise Active vs Inactive Breakdown
769
- if 'state' in df.columns and 'is_active' in df.columns:
770
- state_activity = df.groupby(['state', 'is_active']).size().reset_index(name='count')
771
- state_activity['status'] = state_activity['is_active'].map({True: 'Active', False: 'Inactive'})
772
-
773
- # Get top 10 states by total incidents
774
- top_states = df['state'].value_counts().head(10).index.tolist()
775
- state_activity_filtered = state_activity[state_activity['state'].isin(top_states)]
776
-
777
- if not state_activity_filtered.empty:
778
- fig5 = px.bar(
779
- state_activity_filtered,
780
- x='state',
781
- y='count',
782
- color='status',
783
- title="πŸ—ΊοΈ Active vs Inactive Incidents by State (Top 10)",
784
- labels={'state': 'State', 'count': 'Number of Incidents'},
785
- color_discrete_map={'Active': 'red', 'Inactive': 'gray'}
786
- )
787
- else:
788
- fig5 = px.bar(title="State activity data not available")
789
- else:
790
- fig5 = px.bar(title="State activity data not available")
791
- figures.append(fig5)
792
-
793
- return figures
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- inciweb_df = fetch_inciweb_data()
857
- if inciweb_df.empty:
858
- yield "❌ Failed to fetch InciWeb data", None, None, None, None, None, None
 
 
 
 
 
 
 
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
- inciweb_df = add_coordinates_to_incidents(inciweb_df, max_incidents=15)
 
 
 
 
 
 
 
865
 
866
  yield "πŸ›°οΈ Fetching NASA FIRMS fire detection data...", None, None, None, None, None, None
867
 
868
- # Fetch FIRMS data
869
- firms_df = fetch_firms_data()
870
- if firms_df.empty:
871
- yield "⚠️ FIRMS data unavailable, proceeding with InciWeb only", None, None, inciweb_df, firms_df, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- enhanced_df = match_firms_to_inciweb(inciweb_df, firms_df)
 
 
 
 
 
 
 
 
878
 
879
  yield "πŸ—ΊοΈ Generating enhanced map...", None, None, None, None, None, None
880
 
881
- # Generate map and visualizations
882
- map_html = generate_enhanced_map(enhanced_df, firms_df)
883
- plots = generate_enhanced_visualizations(enhanced_df, firms_df)
 
 
 
 
 
 
 
 
 
 
 
884
 
885
  # Prepare export data - create temporary files
886
- import tempfile
887
-
888
- # Create CSV file
889
- csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
890
- enhanced_df.to_csv(csv_file.name, index=False)
891
- csv_file.close()
 
 
 
 
892
 
893
- active_count = (enhanced_df.get('is_active', pd.Series([False])) == True).sum()
894
- total_hotspots = len(firms_df)
895
-
896
- final_status = f"βœ… Complete! Found {active_count} active fires with {total_hotspots} total hotspots"
897
-
898
- yield (final_status, map_html, plots[0], enhanced_df, firms_df, csv_file.name,
899
- {"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
 
 
 
 
 
 
 
 
 
900
 
901
  except Exception as e:
902
- yield f"❌ Error: {str(e)}", None, None, None, None, None, None
 
 
 
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"""