FIRMS Hotspot Data Processor (Python): Streaming and Streamlining Wildfire Monitoring in QGIS
- Marco G J
- Jul 15, 2024
- 4 min read
Updated: Oct 22, 2024
(in under 20 seconds)
Automated Data Retrieval: Downloads fire hotspot data from multiple FIRMS sources for the last 7 days, tailored to the current display extent in QGIS.
Time Calculation: Calculates the time difference between the current time and the fire detection time, enabling temporal analysis.
Data Consolidation: Combines data from all sources into a single CSV file with a timestamp, ensuring easy access and traceability.
QGIS Integration: Loads the processed data into QGIS as a new layer, complete with custom styling based on the time since detection.
KML Export: Exports KML files categorized by different time ranges of hotspots and the display extent, facilitating further analysis and sharing.
"""
FIRMS Hotspot Data Processor for QGIS
Downloads, processes, and visualizes FIRMS (Fire Information for Resource Management System)
hotspot data within QGIS
Key features:
1. Downloads fire hotspot data from the multiple FIRMS sources for the last 7 days for the current display extent
2. Calculates the time difference between the current time and the fire detection time
4. Creates a combined CSV file with all the processed data
5. Loads the data into QGIS as a new layer with custom styling based on time since detection
6. Exports KML files
CSV file:
The combined CSV file with all processed data is saved in the "~/QGIS_FIRMS_Data" directory.
The filename includes a timestamp: "merged_fires_7days_processed_YYYYMMDD_HHMMSS.csv"
KML files:
Five KML files for different time ranges of hotspots + one kml for display extent
Note: This script requires an active internet connection to fetch data from FIRMS URLs.
"""
import importlibimport subprocessimport sysdef install_package(package): subprocess.check_call([sys.executable, "-m", "pip", "install", package])required_packages = ['requests', 'pytz']for package in required_packages: try: importlib.import_module(package) except ImportError: print(f"{package} not found. Installing...") install_package(package)import loggingimport requestsimport csvfrom datetime import datetimeimport osfrom qgis.core import *from qgis.utils import ifacefrom PyQt5.QtGui import QColorimport pytzfrom qgis.core import ( QgsProject, QgsVectorLayer, QgsSymbol, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsRectangle, QgsFeatureRequest, QgsFeature, QgsGeometry, QgsVectorFileWriter, QgsField)from qgis.PyQt.QtCore import QVariantdef print_message(message): print(f"[FIRMS Hotspot Processor] {message}")print_message("Script started. Processing FIRMS hotspot data...")# Set up logginglogging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)# Directory to save CSV filessave_dir = os.path.expanduser("~/QGIS_FIRMS_Data")os.makedirs(save_dir, exist_ok=True)print_message(f"Data will be saved to: {save_dir}")# URLs for the CSV files with the provided map keybase_urls = [ "https://firms.modaps.eosdis.nasa.gov/mapserver/wfs/Canada/a8fec73c297a9a43dcedd6c8f98ff089/?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAME=ms:fires_modis_7days&SRSNAME=urn:ogc:def:crs:EPSG::4326&outputformat=csv", "https://firms.modaps.eosdis.nasa.gov/mapserver/wfs/Canada/a8fec73c297a9a43dcedd6c8f98ff089/?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAME=ms:fires_snpp_7days&SRSNAME=urn:ogc:def:crs:EPSG::4326&outputformat=csv", "https://firms.modaps.eosdis.nasa.gov/mapserver/wfs/Canada/a8fec73c297a9a43dcedd6c8f98ff089/?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAME=ms:fires_noaa20_7days&SRSNAME=urn:ogc:def:crs:EPSG::4326&outputformat=csv", "https://firms.modaps.eosdis.nasa.gov/mapserver/wfs/Canada/a8fec73c297a9a43dcedd6c8f98ff089/?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAME=ms:fires_noaa21_7days&SRSNAME=urn:ogc:def:crs:EPSG::4326&outputformat=csv"]# Time zone informationutc_zone = pytz.utcpacific_zone = pytz.timezone('America/Los_Angeles')def download_and_process_data(base_url, current_time_utc, extent): print_message(f"Downloading full dataset from: {base_url}") headers = { 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' } response = requests.get(base_url, headers=headers) if response.status_code != 200: print_message(f"Failed to fetch data. Status code: {response.status_code}") return [] csv_content = response.content.decode('utf-8') rows = list(csv.DictReader(csv_content.splitlines())) if not rows: print_message("No data available from this source.") return [] print_message(f"Downloaded {len(rows)} rows. Filtering for current extent...") filtered_rows = [row for row in rows if extent.contains(QgsPointXY(float(row['longitude']), float(row['latitude'])))] print_message(f"Processing {len(filtered_rows)} rows of data...") for row in filtered_rows: if 'acq_date' in row and 'acq_time' in row: acq_datetime_str = f"{row['acq_date']} {row['acq_time']}" acq_datetime = datetime.strptime(acq_datetime_str, '%Y-%m-%d %H%M') acq_datetime_utc = pytz.utc.localize(acq_datetime) acq_datetime_pst = acq_datetime_utc.astimezone(pacific_zone) # Format the date as MM/DD/YYYY formatted_date = acq_datetime_pst.strftime('%m/%d/%Y') # Format the time as HH:MM formatted_time = acq_datetime_pst.strftime('%H:%M') # Combine date, time, and PST indicator row['acq_datetime_PST'] = f"{formatted_date} {formatted_time} PST" time_difference = current_time_utc - acq_datetime_utc row['time_difference_hours'] = time_difference.total_seconds() / 3600.0 return filtered_rowsprint_message("Retrieving current map canvas extent...")canvas = iface.mapCanvas()extent = canvas.extent()if canvas.mapSettings().destinationCrs().authid() != 'EPSG:4326': print_message("Transforming extent to EPSG:4326...") transform = QgsCoordinateTransform(canvas.mapSettings().destinationCrs(), QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance()) extent = transform.transformBoundingBox(extent)print_message("Getting current time in UTC...")current_time_utc = datetime.now(utc_zone)print_message("Fetching and processing data from multiple sources...")all_combined_rows = []for i, base_url in enumerate(base_urls, 1): print_message(f"Processing source {i} of {len(base_urls)}...") all_combined_rows.extend(download_and_process_data(base_url, current_time_utc, extent))print_message(f"Total hotspots found: {len(all_combined_rows)}")if not all_combined_rows: print_message("\n" + "*" * 80) print_message("* NO HOTSPOTS DETECTED IN THE LAST 7 DAYS WITHIN THE CURRENT QGIS MAP EXTENT *") print_message("*" * 80 + "\n")else: print_message("\n" + "*" * 80) print_message(f"* HOTSPOTS FOUND FOR LAST 7 DAYS IN CURRENT DISPLAY EXTENT - {len(all_combined_rows)} HOTSPOTS *") print_message("*" * 80 + "\n") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") combined_csv_filename = os.path.join(save_dir, f"merged_fires_7days_processed_{timestamp}.csv") print_message(f"Writing data to CSV: {combined_csv_filename}") fieldnames = all_combined_rows[0].keys() with open(combined_csv_filename, 'w', newline='') as combined_csv_file: writer = csv.DictWriter(combined_csv_file, fieldnames=fieldnames) writer.writeheader() for row in all_combined_rows: writer.writerow(row) print_message("Loading data into QGIS...") uri = f"file:///{os.path.abspath(combined_csv_filename)}?delimiter=,&xField=longitude&yField=latitude&crs=EPSG:4326" layer = QgsVectorLayer(uri, f"FIRMS Hotspots/Fires in the last 7 days ({timestamp})", "delimitedtext") if not layer.isValid(): print_message(f"Failed to load layer: {combined_csv_filename}") else: print_message("Adding layer to QGIS project...") QgsProject.instance().addMapLayer(layer) # Add the new field to the layer layer.startEditing() layer.addAttribute(QgsField("acq_datetime_PST", QVariant.String)) layer.updateFields() # Populate the new field for feature in layer.getFeatures(): feature["acq_datetime_PST"] = feature["acq_datetime_PST"] layer.updateFeature(feature) layer.commitChanges() print_message("Applying custom styling to the layer...") ranges = [] for class_def in [ {"name": "< 6h", "min": 0, "max": 6, "color": "purple"}, {"name": "6 - 12h", "min": 6, "max": 12, "color": "red"}, {"name": "12 - 24h", "min": 12, "max": 24, "color": "orange"}, {"name": "24 - 48h", "min": 24, "max": 48, "color": "yellow"}, {"name": "48h - 7days", "min": 48, "max": float("inf"), "color": QColor(204, 255, 0)} ]: symbol = QgsSymbol.defaultSymbol(layer.geometryType()) symbol.setColor(QColor(class_def["color"])) symbol.setSize(12) symbol.symbolLayer(0).setStrokeWidth(1.2) range = QgsRendererRange(class_def["min"], class_def["max"], symbol, class_def["name"]) ranges.append(range) renderer = QgsGraduatedSymbolRenderer('time_difference_hours', ranges) renderer.setMode(QgsGraduatedSymbolRenderer.Custom) layer.setRenderer(renderer) layer.triggerRepaint() print_message("Preparing KML exports...") kml_output_dir = os.path.expanduser(f"~/desktop/FIRMShotspots_scriptRun_{timestamp}") os.makedirs(kml_output_dir, exist_ok=True) class_definitions = [ {"name": "less_than_6h", "min": 0, "max": 6}, {"name": "6_to_12h", "min": 6, "max": 12}, {"name": "12_to_24h", "min": 12, "max": 24}, {"name": "24_to_48h", "min": 24, "max": 48}, {"name": "48h_to_7days", "min": 48, "max": float("inf")} ] for class_def in class_definitions: print_message(f"Exporting KML for hotspots {class_def['name']}...") class_layer = QgsVectorLayer("Point?crs=EPSG:4326", f"Fires {class_def['name']}", "memory") class_provider = class_layer.dataProvider() class_provider.addAttributes(layer.fields()) class_layer.updateFields() features = [] for feature in layer.getFeatures(): time_diff = feature['time_difference_hours'] if class_def['min'] <= time_diff < class_def['max']: features.append(feature) class_provider.addFeatures(features) class_kml_path = os.path.join(kml_output_dir, f"fires_{class_def['name']}.kml") save_options = QgsVectorFileWriter.SaveVectorOptions() save_options.driverName = "KML" QgsVectorFileWriter.writeAsVectorFormatV2(class_layer, class_kml_path, QgsCoordinateTransformContext(), save_options) print_message("Exporting map extent to KML...") extent_layer = QgsVectorLayer("Polygon?crs=EPSG:4326", "Clipping_Extent", "memory") extent_provider = extent_layer.dataProvider() extent_provider.addAttributes([QgsField("id", QVariant.Int)]) extent_layer.updateFields() extent_feature = QgsFeature() extent_feature.setGeometry(QgsGeometry.fromRect(extent)) extent_feature.setAttributes([1]) extent_provider.addFeature(extent_feature) extent_kml_file_path = os.path.join(kml_output_dir, "clipping_extent.kml") QgsVectorFileWriter.writeAsVectorFormat(extent_layer, extent_kml_file_path, "utf-8", extent_layer.crs(), "KML") print_message("\n" + "*" * 80) print_message("*" * 80) print_message(f"* KML FILES HAVE BEEN SAVED TO: *") print_message(f"* {kml_output_dir} *") print_message("*" * 80) print_message("*" * 80 + "\n")print_message("Script completed successfully. FIRMS hotspot data has been processed and loaded into QGIS.")



Comments