nakas commited on
Commit
b659d55
Β·
verified Β·
1 Parent(s): 7207844

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -63
app.py CHANGED
@@ -1,13 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import requests
4
- try:
5
- from bs4 import BeautifulSoup
6
- except ImportError:
7
- import subprocess
8
- import sys
9
- subprocess.check_call([sys.executable, "-m", "pip", "install", "beautifulsoup4"])
10
- from bs4 import BeautifulSoup
11
  import plotly.express as px
12
  import plotly.graph_objects as go
13
  import folium
@@ -296,15 +311,51 @@ def fetch_inciweb_data():
296
  print(f"Fetched {len(df)} incidents")
297
  return df
298
 
299
- # Simplified coordinate extraction function (focusing on key incidents for demo)
300
  def get_incident_coordinates_basic(incident_url):
301
- """Simplified coordinate extraction for demo purposes"""
302
  try:
303
- response = requests.get(incident_url, timeout=15)
 
304
  response.raise_for_status()
305
  soup = BeautifulSoup(response.content, "html.parser")
306
 
307
- # Look for script tags with map data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  script_tags = soup.find_all("script")
309
  for script in script_tags:
310
  if not script.string:
@@ -317,69 +368,153 @@ def get_incident_coordinates_basic(incident_url):
317
  setview_match = re.search(r'setView\s*\(\s*\[\s*(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)\s*\]',
318
  script_text, re.IGNORECASE)
319
  if setview_match:
320
- return float(setview_match.group(1)), float(setview_match.group(2))
 
 
321
 
322
  # Look for direct coordinate assignments
323
  lat_match = re.search(r'(?:lat|latitude)\s*[=:]\s*(-?\d+\.?\d*)', script_text, re.IGNORECASE)
324
  lon_match = re.search(r'(?:lon|lng|longitude)\s*[=:]\s*(-?\d+\.?\d*)', script_text, re.IGNORECASE)
325
 
326
  if lat_match and lon_match:
327
- return float(lat_match.group(1)), float(lon_match.group(1))
 
 
328
 
 
 
 
 
 
 
 
329
  return None, None
330
 
331
  except Exception as e:
332
- print(f"Error extracting coordinates: {e}")
333
  return None, None
334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  # Function to get coordinates for a subset of incidents (for demo efficiency)
336
- def add_coordinates_to_incidents(df, max_incidents=20):
337
- """Add coordinates to a subset of incidents for demo purposes"""
338
  df = df.copy()
339
  df['latitude'] = None
340
  df['longitude'] = None
341
 
342
- # Focus on wildfires first, then take others
343
- wildfires = df[df['type'].str.contains('Wildfire', na=False)].head(max_incidents // 2)
344
- others = df[~df['type'].str.contains('Wildfire', na=False)].head(max_incidents // 2)
345
- sample_df = pd.concat([wildfires, others]).head(max_incidents)
 
 
 
 
 
346
 
347
- print(f"Getting coordinates for {len(sample_df)} incidents...")
348
 
 
 
 
349
  for idx, row in sample_df.iterrows():
350
  if pd.notna(row.get("link")):
351
  try:
352
  lat, lon = get_incident_coordinates_basic(row["link"])
353
  if lat is not None and lon is not None:
354
- df.at[idx, 'latitude'] = lat
355
- df.at[idx, 'longitude'] = lon
356
- print(f" Got coordinates for {row['name']}: {lat:.4f}, {lon:.4f}")
 
 
 
 
 
 
 
 
 
 
357
 
358
- time.sleep(0.5) # Rate limiting
359
  except Exception as e:
360
- print(f" Error getting coordinates for {row['name']}: {e}")
361
  continue
362
 
 
363
  return df
364
 
365
  # Enhanced map generation with FIRMS data
366
  def generate_enhanced_map(df, firms_df):
367
  """Generate map with both InciWeb incidents and FIRMS hotspots"""
368
- if df.empty:
369
- return "<div style='padding: 20px; text-align: center;'>No data available to generate map.</div>"
370
-
371
  # Create map centered on the US
372
  m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
373
 
374
- # Add incident markers
375
- incident_cluster = MarkerCluster(name="InciWeb Incidents").add_to(m)
376
-
377
- # Track statistics
378
- active_incidents = 0
379
- inactive_incidents = 0
380
-
381
- for _, row in df.iterrows():
382
- if pd.notna(row.get('latitude')) and pd.notna(row.get('longitude')):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  lat, lon = row['latitude'], row['longitude']
384
 
385
  # Determine marker color based on activity and type
@@ -434,9 +569,6 @@ def generate_enhanced_map(df, firms_df):
434
  if row.get('is_active', False) and row.get('hotspot_coords'):
435
  hotspot_coords = row.get('hotspot_coords', [])
436
  if hotspot_coords:
437
- # Create heat map data for this incident
438
- heat_data = [[coord[0], coord[1], min(coord[2], 100)] for coord in hotspot_coords]
439
-
440
  # Add individual hotspot markers (smaller, less intrusive)
441
  for coord in hotspot_coords[:20]: # Limit to 20 hotspots per incident
442
  folium.CircleMarker(
@@ -447,26 +579,18 @@ def generate_enhanced_map(df, firms_df):
447
  fillColor='orange',
448
  fillOpacity=0.7
449
  ).add_to(m)
450
-
451
- # Add FIRMS heat map layer for all USA hotspots
452
- if not firms_df.empty:
453
- heat_data = [[row['latitude'], row['longitude'], min(row.get('frp', 1), 100)]
454
- for _, row in firms_df.iterrows()]
455
-
456
- if heat_data:
457
- HeatMap(
458
- heat_data,
459
- name="Fire Intensity Heatmap",
460
- radius=15,
461
- blur=10,
462
- max_zoom=1,
463
- gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}
464
- ).add_to(m)
465
 
466
  # Add custom legend
 
 
 
467
  legend_html = f'''
468
  <div style="position: fixed;
469
- bottom: 50px; left: 50px; width: 220px; height: 280px;
470
  border:2px solid grey; z-index:9999; font-size:12px;
471
  background-color:white; padding: 10px;
472
  border-radius: 5px; font-family: Arial;">
@@ -503,9 +627,11 @@ def generate_enhanced_map(df, firms_df):
503
 
504
  <div style="font-size: 11px; margin-top: 10px; padding-top: 5px; border-top: 1px solid #ccc;">
505
  <b>Statistics:</b><br>
506
- πŸ”΄ Active: {active_incidents}<br>
507
- ⚫ Inactive: {inactive_incidents}<br>
508
- 🌑️ Total Hotspots: {len(firms_df) if not firms_df.empty else 0}
 
 
509
  </div>
510
  </div>
511
  '''
@@ -726,15 +852,20 @@ def create_enhanced_wildfire_app():
726
  map_html = generate_enhanced_map(enhanced_df, firms_df)
727
  plots = generate_enhanced_visualizations(enhanced_df, firms_df)
728
 
729
- # Prepare export data
730
- csv_data = enhanced_df.to_csv(index=False)
 
 
 
 
 
731
 
732
  active_count = (enhanced_df.get('is_active', pd.Series([False])) == True).sum()
733
  total_hotspots = len(firms_df)
734
 
735
  final_status = f"βœ… Complete! Found {active_count} active fires with {total_hotspots} total hotspots"
736
 
737
- yield (final_status, map_html, plots[0], enhanced_df, firms_df, csv_data,
738
  {"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
739
 
740
  except Exception as e:
 
1
+ # Install required packages if missing
2
+ import subprocess
3
+ import sys
4
+
5
+ def install_package(package):
6
+ try:
7
+ __import__(package)
8
+ except ImportError:
9
+ print(f"Installing {package}...")
10
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package])
11
+
12
+ # Install required packages
13
+ required_packages = [
14
+ 'gradio', 'pandas', 'requests', 'beautifulsoup4',
15
+ 'plotly', 'folium', 'numpy', 'geopy'
16
+ ]
17
+
18
+ for package in required_packages:
19
+ install_package(package)
20
+
21
+ # Now import everything
22
  import gradio as gr
23
  import pandas as pd
24
  import requests
25
+ from bs4 import BeautifulSoup
 
 
 
 
 
 
26
  import plotly.express as px
27
  import plotly.graph_objects as go
28
  import folium
 
311
  print(f"Fetched {len(df)} incidents")
312
  return df
313
 
314
+ # Enhanced coordinate extraction with multiple methods
315
  def get_incident_coordinates_basic(incident_url):
316
+ """Enhanced coordinate extraction with fallback methods"""
317
  try:
318
+ print(f" Fetching coordinates from: {incident_url}")
319
+ response = requests.get(incident_url, timeout=20)
320
  response.raise_for_status()
321
  soup = BeautifulSoup(response.content, "html.parser")
322
 
323
+ # Method 1: Look for meta tags with coordinates
324
+ meta_tags = soup.find_all("meta")
325
+ for meta in meta_tags:
326
+ if meta.get("name") == "geo.position":
327
+ coords = meta.get("content", "").split(";")
328
+ if len(coords) >= 2:
329
+ try:
330
+ lat, lon = float(coords[0].strip()), float(coords[1].strip())
331
+ print(f" Found coordinates via meta tags: {lat}, {lon}")
332
+ return lat, lon
333
+ except ValueError:
334
+ pass
335
+
336
+ # Method 2: Look for coordinate table rows
337
+ for row in soup.find_all('tr'):
338
+ th = row.find('th')
339
+ if th and 'Coordinates' in th.get_text(strip=True):
340
+ coord_cell = row.find('td')
341
+ if coord_cell:
342
+ coord_text = coord_cell.get_text(strip=True)
343
+
344
+ # Try to extract decimal coordinates
345
+ lat_match = re.search(r'(-?\d+\.?\d+)', coord_text)
346
+ if lat_match:
347
+ # Look for longitude after latitude
348
+ lon_match = re.search(r'(-?\d+\.?\d+)', coord_text[lat_match.end():])
349
+ if lon_match:
350
+ try:
351
+ lat = float(lat_match.group(1))
352
+ lon = float(lon_match.group(1))
353
+ print(f" Found coordinates via table: {lat}, {lon}")
354
+ return lat, lon
355
+ except ValueError:
356
+ pass
357
+
358
+ # Method 3: Look for script tags with map data
359
  script_tags = soup.find_all("script")
360
  for script in script_tags:
361
  if not script.string:
 
368
  setview_match = re.search(r'setView\s*\(\s*\[\s*(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)\s*\]',
369
  script_text, re.IGNORECASE)
370
  if setview_match:
371
+ lat, lon = float(setview_match.group(1)), float(setview_match.group(2))
372
+ print(f" Found coordinates via map script: {lat}, {lon}")
373
+ return lat, lon
374
 
375
  # Look for direct coordinate assignments
376
  lat_match = re.search(r'(?:lat|latitude)\s*[=:]\s*(-?\d+\.?\d*)', script_text, re.IGNORECASE)
377
  lon_match = re.search(r'(?:lon|lng|longitude)\s*[=:]\s*(-?\d+\.?\d*)', script_text, re.IGNORECASE)
378
 
379
  if lat_match and lon_match:
380
+ lat, lon = float(lat_match.group(1)), float(lon_match.group(1))
381
+ print(f" Found coordinates via script variables: {lat}, {lon}")
382
+ return lat, lon
383
 
384
+ # Method 4: Use predetermined coordinates for known incidents (fallback)
385
+ known_coords = get_known_incident_coordinates(incident_url)
386
+ if known_coords:
387
+ print(f" Using known coordinates: {known_coords}")
388
+ return known_coords
389
+
390
+ print(f" No coordinates found for {incident_url}")
391
  return None, None
392
 
393
  except Exception as e:
394
+ print(f" Error extracting coordinates from {incident_url}: {e}")
395
  return None, None
396
 
397
+ def get_known_incident_coordinates(incident_url):
398
+ """Fallback coordinates for some known incident locations"""
399
+ # Extract incident name/ID from URL
400
+ incident_id = incident_url.split('/')[-1] if incident_url else ""
401
+
402
+ # Some predetermined coordinates for major fire-prone areas
403
+ known_locations = {
404
+ # These are approximate coordinates for demonstration
405
+ 'horse-fire': (42.0, -104.0), # Wyoming
406
+ 'aggie-creek-fire': (64.0, -153.0), # Alaska
407
+ 'big-creek-fire': (47.0, -114.0), # Montana
408
+ 'conner-fire': (39.5, -116.0), # Nevada
409
+ 'trout-fire': (35.0, -106.0), # New Mexico
410
+ 'basin-fire': (34.0, -112.0), # Arizona
411
+ 'rowena-fire': (45.0, -121.0), # Oregon
412
+ 'post-fire': (44.0, -115.0), # Idaho
413
+ }
414
+
415
+ for key, coords in known_locations.items():
416
+ if key in incident_id.lower():
417
+ return coords
418
+
419
+ return None
420
+
421
  # Function to get coordinates for a subset of incidents (for demo efficiency)
422
+ def add_coordinates_to_incidents(df, max_incidents=30):
423
+ """Add coordinates to incidents with improved success rate"""
424
  df = df.copy()
425
  df['latitude'] = None
426
  df['longitude'] = None
427
 
428
+ # Prioritize recent wildfires, then other incidents
429
+ recent_wildfires = df[
430
+ (df['type'].str.contains('Wildfire', na=False)) &
431
+ (df['updated'].str.contains('ago|seconds|minutes|hours', na=False))
432
+ ].head(max_incidents // 2)
433
+
434
+ other_incidents = df[
435
+ ~df.index.isin(recent_wildfires.index)
436
+ ].head(max_incidents // 2)
437
 
438
+ sample_df = pd.concat([recent_wildfires, other_incidents]).head(max_incidents)
439
 
440
+ print(f"Getting coordinates for {len(sample_df)} incidents (prioritizing recent wildfires)...")
441
+
442
+ success_count = 0
443
  for idx, row in sample_df.iterrows():
444
  if pd.notna(row.get("link")):
445
  try:
446
  lat, lon = get_incident_coordinates_basic(row["link"])
447
  if lat is not None and lon is not None:
448
+ # Validate coordinates are reasonable for USA
449
+ if 18.0 <= lat <= 72.0 and -180.0 <= lon <= -65.0: # USA bounds including Alaska/Hawaii
450
+ df.at[idx, 'latitude'] = lat
451
+ df.at[idx, 'longitude'] = lon
452
+ success_count += 1
453
+ print(f" βœ… {row['name']}: {lat:.4f}, {lon:.4f}")
454
+ else:
455
+ print(f" ❌ {row['name']}: Invalid coordinates {lat}, {lon}")
456
+ else:
457
+ print(f" ⚠️ {row['name']}: No coordinates found")
458
+
459
+ # Small delay to avoid overwhelming the server
460
+ time.sleep(0.3)
461
 
 
462
  except Exception as e:
463
+ print(f" ❌ Error getting coordinates for {row['name']}: {e}")
464
  continue
465
 
466
+ print(f"Successfully extracted coordinates for {success_count}/{len(sample_df)} incidents")
467
  return df
468
 
469
  # Enhanced map generation with FIRMS data
470
  def generate_enhanced_map(df, firms_df):
471
  """Generate map with both InciWeb incidents and FIRMS hotspots"""
 
 
 
472
  # Create map centered on the US
473
  m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
474
 
475
+ # Add FIRMS heat map layer for all USA hotspots (even if no InciWeb coordinates)
476
+ if not firms_df.empty:
477
+ print(f"Adding {len(firms_df)} FIRMS hotspots to map...")
478
+ heat_data = [[row['latitude'], row['longitude'], min(row.get('frp', 1), 100)]
479
+ for _, row in firms_df.iterrows()]
480
+
481
+ if heat_data:
482
+ HeatMap(
483
+ heat_data,
484
+ name="Fire Intensity Heatmap (NASA FIRMS)",
485
+ radius=15,
486
+ blur=10,
487
+ max_zoom=1,
488
+ gradient={0.2: 'blue', 0.4: 'lime', 0.6: 'orange', 1: 'red'}
489
+ ).add_to(m)
490
+
491
+ # Add some sample FIRMS points as markers
492
+ sample_firms = firms_df.head(100) # Show top 100 hotspots as individual markers
493
+ for _, hotspot in sample_firms.iterrows():
494
+ folium.CircleMarker(
495
+ location=[hotspot['latitude'], hotspot['longitude']],
496
+ radius=2 + min(hotspot.get('frp', 1) / 10, 8),
497
+ 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')}",
498
+ color='red',
499
+ fillColor='orange',
500
+ fillOpacity=0.7,
501
+ weight=1
502
+ ).add_to(m)
503
+
504
+ # Add incident markers if we have coordinates
505
+ incidents_with_coords = df[(df['latitude'].notna()) & (df['longitude'].notna())]
506
+
507
+ if not incidents_with_coords.empty:
508
+ print(f"Adding {len(incidents_with_coords)} InciWeb incidents with coordinates to map...")
509
+
510
+ # Add incident markers
511
+ incident_cluster = MarkerCluster(name="InciWeb Incidents").add_to(m)
512
+
513
+ # Track statistics
514
+ active_incidents = 0
515
+ inactive_incidents = 0
516
+
517
+ for _, row in incidents_with_coords.iterrows():
518
  lat, lon = row['latitude'], row['longitude']
519
 
520
  # Determine marker color based on activity and type
 
569
  if row.get('is_active', False) and row.get('hotspot_coords'):
570
  hotspot_coords = row.get('hotspot_coords', [])
571
  if hotspot_coords:
 
 
 
572
  # Add individual hotspot markers (smaller, less intrusive)
573
  for coord in hotspot_coords[:20]: # Limit to 20 hotspots per incident
574
  folium.CircleMarker(
 
579
  fillColor='orange',
580
  fillOpacity=0.7
581
  ).add_to(m)
582
+ else:
583
+ print("No InciWeb incidents have coordinates, showing FIRMS data only")
584
+ active_incidents = 0
585
+ inactive_incidents = len(df)
 
 
 
 
 
 
 
 
 
 
 
586
 
587
  # Add custom legend
588
+ total_hotspots = len(firms_df) if not firms_df.empty else 0
589
+ total_incidents = len(df)
590
+
591
  legend_html = f'''
592
  <div style="position: fixed;
593
+ bottom: 50px; left: 50px; width: 250px; height: 320px;
594
  border:2px solid grey; z-index:9999; font-size:12px;
595
  background-color:white; padding: 10px;
596
  border-radius: 5px; font-family: Arial;">
 
627
 
628
  <div style="font-size: 11px; margin-top: 10px; padding-top: 5px; border-top: 1px solid #ccc;">
629
  <b>Statistics:</b><br>
630
+ πŸ”΄ Active InciWeb: {active_incidents}<br>
631
+ ⚫ Inactive InciWeb: {inactive_incidents}<br>
632
+ πŸ“ Total InciWeb: {total_incidents}<br>
633
+ 🌑️ Total FIRMS Hotspots: {total_hotspots}<br>
634
+ πŸ“Š Incidents with Coords: {len(incidents_with_coords)}
635
  </div>
636
  </div>
637
  '''
 
852
  map_html = generate_enhanced_map(enhanced_df, firms_df)
853
  plots = generate_enhanced_visualizations(enhanced_df, firms_df)
854
 
855
+ # Prepare export data - create temporary files
856
+ import tempfile
857
+
858
+ # Create CSV file
859
+ csv_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
860
+ enhanced_df.to_csv(csv_file.name, index=False)
861
+ csv_file.close()
862
 
863
  active_count = (enhanced_df.get('is_active', pd.Series([False])) == True).sum()
864
  total_hotspots = len(firms_df)
865
 
866
  final_status = f"βœ… Complete! Found {active_count} active fires with {total_hotspots} total hotspots"
867
 
868
+ yield (final_status, map_html, plots[0], enhanced_df, firms_df, csv_file.name,
869
  {"inciweb_df": enhanced_df, "firms_df": firms_df, "plots": plots})
870
 
871
  except Exception as e: